diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 889eae1..9470333 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,16 @@ jobs: - stm32f777 - stm32f778 - stm32f779 + - stm32h735 + - stm32h742 + - stm32h742v + - stm32h743 + - stm32h743v + - stm32h747cm7 + - stm32h750 + - stm32h750v + - stm32h753 + - stm32h753v steps: - name: Checkout uses: actions/checkout@v3 @@ -110,6 +120,7 @@ jobs: - stm32f107 - stm32f407 - stm32f745 + - stm32h735 steps: - name: Checkout uses: actions/checkout@v3 @@ -182,6 +193,7 @@ jobs: - stm32f107 - stm32f429 - stm32f745 + - stm32h735 pins: - nucleo - default diff --git a/Cargo.toml b/Cargo.toml index 7ed3bab..2f14b01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ features = ["smoltcp-phy", "stm32f429", "async-await"] [dependencies] volatile-register = "0.2" aligned = "0.4" +stm32h7xx-hal = { version = "0.13", optional = true } stm32f7xx-hal = { version = "0.7.0", optional = true } stm32f4xx-hal = { version = "0.14", optional = true } stm32f4 = { version = "0.15", optional = true } @@ -41,8 +42,14 @@ default = [ "defmt", "ptp" ] device-selected = [] fence = [] ptp = [ "smoltcp/packetmeta-id" ] +f-series = [ ] async-await = ["dep:futures"] +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"] stm32f407 = ["stm32f4xx-hal/stm32f407", "stm32f4", "device-selected"] @@ -64,6 +71,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] diff --git a/README.md b/README.md index 57fd96b..c4810cb 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ use stm32_eth::{ hal::gpio::GpioExt, hal::rcc::RccExt, stm32::Peripherals, - dma::{RxRingEntry, TxRingEntry}, + dma::{MTU, TxDescriptorRing, TxDescriptor, RxDescriptorRing, RxDescriptor}, EthPins, }; use fugit::RateExtU32; @@ -54,20 +54,29 @@ 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, + #[cfg(any(feature = "stm32f1xx-hal", feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] mmc: p.ETHERNET_MMC, + #[cfg(feature = "stm32h7xx-hal")] + mtl: p.ETHERNET_MTL, dma: p.ETHERNET_DMA, + #[cfg(any(feature = "stm32f1xx-hal", feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] ptp: p.ETHERNET_PTP, }; let stm32_eth::Parts { dma: mut eth_dma, mac: _, ptp: _ } = stm32_eth::new( parts, - &mut rx_ring[..], - &mut tx_ring[..], + rx_ring, + tx_ring, clocks, eth_pins, ) 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 ac0adca..b69dab2 100644 --- a/examples/arp.rs +++ b/examples/arp.rs @@ -23,7 +23,7 @@ use stm32_eth::{ 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 (rx_ring, tx_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,6 +101,8 @@ fn main() -> ! { buf[38..42].copy_from_slice(&TARGET_IP); }); + stm32_eth::eth_interrupt_handler(); + match r { Ok(()) => { defmt::info!("ARP sent"); diff --git a/examples/async-rtic-timestamp.rs b/examples/async-rtic-timestamp.rs index 2f49083..b0a058d 100644 --- a/examples/async-rtic-timestamp.rs +++ b/examples/async-rtic-timestamp.rs @@ -38,6 +38,10 @@ mod common; extern crate async_rtic as rtic; +defmt::timestamp!("{=u64:us}", { + (stm32_eth::ptp::EthernetPTP::now().total_nanos() / 1_000) as u64 +}); + #[rtic::app(device = stm32_eth::stm32, dispatchers = [SPI1])] mod app { @@ -51,7 +55,7 @@ mod app { use ieee802_3_miim::{phy::PhySpeed, Phy}; use stm32_eth::{ - dma::{EthernetDMA, PacketId, RxRing, RxRingEntry, TxRing, TxRingEntry}, + dma::{EthernetDMA, PacketId, RxRing, TxRing}, mac::Speed, ptp::{EthernetPTP, Subseconds, Timestamp}, Parts, @@ -66,8 +70,6 @@ mod app { struct Shared {} #[init(local = [ - rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], - tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], dma: MaybeUninit> = MaybeUninit::uninit(), arbiter: MaybeUninit > = MaybeUninit::uninit(), // We use a channel to signal when 1 second has passed. @@ -79,8 +81,7 @@ mod app { defmt::info!("Pre-init"); let p = cx.device; - let rx_ring = cx.local.rx_ring; - let tx_ring = cx.local.tx_ring; + let (rx_ring, tx_ring) = crate::common::setup_rings(); let (clocks, gpio, ethernet) = crate::common::setup_peripherals(p); @@ -162,7 +163,7 @@ mod app { continue; }; - defmt::debug!("RX timestamp: {}", rx_timestamp); + defmt::info!("RX timestamp: {}", rx_timestamp); if dst_mac == [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56] { let mut timestamp_data = [0u8; 8]; diff --git a/examples/common.rs b/examples/common.rs index c1bb947..02691bb 100644 --- a/examples/common.rs +++ b/examples/common.rs @@ -5,14 +5,23 @@ //! //! Note that this module isn't an example by itself. +use core::mem::MaybeUninit; + use defmt_rtt as _; use panic_probe as _; use stm32_eth::{ - hal::{gpio::GpioExt, rcc::Clocks}, + dma::{RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU}, + hal::gpio::GpioExt, PartsIn, }; +#[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; @@ -24,6 +33,33 @@ use stm32_eth::hal::rcc::RccExt; #[allow(unused)] fn main() {} +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() -> (RxDescriptorRing<'static>, TxDescriptorRing<'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]) }; + + ( + RxDescriptorRing::new(rx_desc, rx_buf), + TxDescriptorRing::new(tx_desc, tx_buf), + ) +} + /// Setup the clocks and return clocks and a GPIO struct that /// can be used to set up all of the pins. /// @@ -33,8 +69,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, }; @@ -109,6 +148,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.vos1().freeze(); + + let rcc = rcc.hclk(200.MHz()).sys_ck(200.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::*; @@ -290,6 +363,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 41c1698..794df65 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,15 @@ 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 (rx_ring, tx_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 ethernet_addr = EthernetAddress(SRC_MAC); diff --git a/examples/pktgen.rs b/examples/pktgen.rs index c226116..528321a 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; @@ -42,21 +42,14 @@ fn main() -> ! { 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 (rx_ring, tx_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(); // Main loop diff --git a/examples/rtic-echo.rs b/examples/rtic-echo.rs index a73f41c..12c6657 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 (rx_ring, tx_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 f576647..bffda43 100644 --- a/examples/rtic-timestamp.rs +++ b/examples/rtic-timestamp.rs @@ -45,7 +45,7 @@ mod app { use systick_monotonic::Systick; use stm32_eth::{ - dma::{EthernetDMA, PacketId, RxRingEntry, TxRingEntry}, + dma::{EthernetDMA, PacketId}, mac::Speed, ptp::{EthernetPTP, Timestamp}, Parts, @@ -65,17 +65,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 (rx_ring, tx_ring) = crate::common::setup_rings(); let (clocks, gpio, ethernet) = crate::common::setup_peripherals(p); let mono = Systick::new(core.SYST, clocks.hclk().raw()); @@ -272,14 +268,21 @@ mod app { } if let Some((tx_id, sent_time)) = tx_id.take() { - if let Poll::Ready(Ok(Some(ts))) = dma.poll_timestamp(&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"); + loop { + let ts = dma.poll_timestamp(&PacketId(tx_id)); + + match ts { + Poll::Pending => continue, + Poll::Ready(Ok(Some(ts))) => { + defmt::info!("TX timestamp: {}", ts); + defmt::debug!("Diff between TX timestamp and the time that was put into the packet: {}", ts - sent_time); + break; + } + Poll::Ready(_) => { + defmt::warn!("Failed to retrieve TX timestamp"); + break; + } + } } } }); diff --git a/examples/smoltcp-timesync/client.rs b/examples/smoltcp-timesync/client.rs index 604ddf7..9dd33c2 100644 --- a/examples/smoltcp-timesync/client.rs +++ b/examples/smoltcp-timesync/client.rs @@ -59,7 +59,7 @@ mod app { use systick_monotonic::Systick; use stm32_eth::{ - dma::{EthernetDMA, RxRingEntry, TxRingEntry}, + dma::EthernetDMA, mac::Speed, ptp::{EthernetPTP, Timestamp}, Parts, @@ -90,8 +90,6 @@ mod app { type Monotonic = Systick<1000>; #[init(local = [ - rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], - tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], rx_meta_storage: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8], rx_payload_storage: [u8; 1024] = [0u8; 1024], tx_meta_storage: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8], @@ -103,8 +101,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 (rx_ring, tx_ring) = crate::common::setup_rings(); let rx_meta_storage = cx.local.rx_meta_storage; let rx_payload_storage = cx.local.rx_payload_storage; diff --git a/examples/smoltcp-timesync/server.rs b/examples/smoltcp-timesync/server.rs index fb5627d..cb4271c 100644 --- a/examples/smoltcp-timesync/server.rs +++ b/examples/smoltcp-timesync/server.rs @@ -25,11 +25,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::{Config, Interface, SocketHandle, SocketSet, SocketStorage}, @@ -59,8 +55,6 @@ mod app { type Monotonic = Systick<1000>; #[init(local = [ - rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], - tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], rx_meta_storage: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8], rx_payload_storage: [u8; 1024] = [0u8; 1024], tx_meta_storage: [udp::PacketMetadata; 8] = [udp::PacketMetadata::EMPTY; 8], @@ -72,8 +66,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 (rx_ring, tx_ring) = crate::common::setup_rings(); let rx_meta_storage = cx.local.rx_meta_storage; let rx_payload_storage = cx.local.rx_payload_storage; diff --git a/examples/timesync/client.rs b/examples/timesync/client.rs index 8c30256..97ad322 100644 --- a/examples/timesync/client.rs +++ b/examples/timesync/client.rs @@ -86,7 +86,7 @@ mod app { use systick_monotonic::Systick; use stm32_eth::{ - dma::{EthernetDMA, RxRingEntry, TxRingEntry}, + dma::EthernetDMA, mac::Speed, ptp::{EthernetPTP, Timestamp}, Parts, @@ -111,17 +111,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 (rx_ring, tx_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/timesync/server.rs b/examples/timesync/server.rs index 5d59a3f..1a2b93b 100644 --- a/examples/timesync/server.rs +++ b/examples/timesync/server.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}; const BROADCAST: [u8; 6] = [0xFF; 6]; const CLIENT_ADDR: [u8; 6] = [0x80, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; @@ -49,17 +45,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 (rx_ring, tx_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/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/cache.rs b/src/dma/cache.rs new file mode 100644 index 0000000..e05da50 --- /dev/null +++ b/src/dma/cache.rs @@ -0,0 +1,85 @@ +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +use super::PacketId; + +/// A cache for timestamping information, +/// used to keep the size of Descriptors down. +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct Cache { + has_packet_id: bool, + #[cfg(feature = "ptp")] + has_timestamp: bool, + + packet_id: PacketId, + #[cfg(feature = "ptp")] + timestamp: Timestamp, +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} + +impl Cache { + pub const fn new() -> Self { + Self { + has_packet_id: false, + packet_id: PacketId(0), + + #[cfg(feature = "ptp")] + has_timestamp: false, + #[cfg(feature = "ptp")] + timestamp: Timestamp::new_raw(0), + } + } + + /// Set the packet ID of this [`Cache`] + /// + /// Removes the timestamp id if `ptp` feature is enabled. + pub fn set_id_and_clear_ts(&mut self, packet_id: Option) { + #[cfg(feature = "ptp")] + { + self.has_timestamp = false; + } + + if let Some(id) = packet_id { + self.packet_id = id; + self.has_packet_id = true + } else { + self.has_packet_id = false; + } + } + + // NOTE(unused): this function is not used if not(feature = "ptp") + #[allow(unused)] + pub fn id(&self) -> Option { + if self.has_packet_id { + Some(self.packet_id) + } else { + None + } + } +} + +#[cfg(feature = "ptp")] +impl Cache { + pub fn set_ts(&mut self, timestamp: Option) { + if let Some(ts) = timestamp { + self.timestamp = ts; + self.has_timestamp = true; + } else { + self.has_timestamp = false; + } + } + + pub fn ts(&self) -> Option { + if self.has_timestamp { + Some(self.timestamp) + } else { + None + } + } +} diff --git a/src/dma/desc.rs b/src/dma/desc.rs deleted file mode 100644 index c6c4f4c..0000000 --- a/src/dma/desc.rs +++ /dev/null @@ -1,66 +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]), - } - } - - pub unsafe fn clear(&mut self) { - (0..DESC_SIZE).for_each(|i| self.write(i, 0)); - } - - 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/generic_ring.rs b/src/dma/generic_ring.rs new file mode 100644 index 0000000..1803ef2 --- /dev/null +++ b/src/dma/generic_ring.rs @@ -0,0 +1,146 @@ +use volatile_register::{RO, RW}; + +use crate::dma::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 descriptor(&self, index: usize) -> &T { + &self.descriptors[index] + } + + pub fn get(&self, index: usize) -> (&T, &[u8]) { + (&self.descriptors[index], &self.buffers[index]) + } + + pub fn get_mut(&mut self, index: usize) -> (&mut T, &mut [u8]) { + (&mut self.descriptors[index], &mut self.buffers[index]) + } + + pub fn get_mut_and_next(&mut self, index: usize) -> (&mut T, &mut [u8], &mut T, &mut [u8]) { + let next = (index + 1) % self.len(); + + macro_rules! mut_and_next { + ($array:expr) => {{ + let (index_slice, next_slice) = if next == 0 { + let (next, index) = $array.split_at_mut(1); + (index, next) + } else { + $array.split_at_mut(next) + }; + + (&mut index_slice[index_slice.len() - 1], &mut next_slice[0]) + }}; + } + + let (desc_index, desc_next) = mut_and_next!(self.descriptors); + let (buf_index, buf_next) = mut_and_next!(self.buffers); + + (desc_index, buf_index, desc_next, buf_next) + } + + 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/mod.rs b/src/dma/mod.rs index 561bd67..70b0956 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -4,6 +4,9 @@ use cortex_m::peripheral::NVIC; use crate::{peripherals::ETHERNET_DMA, stm32::Interrupt}; +mod cache; +pub(crate) use cache::Cache; + #[cfg(feature = "smoltcp-phy")] mod smoltcp_phy; #[cfg(feature = "smoltcp-phy")] @@ -15,15 +18,17 @@ use futures::task::AtomicWaker; #[cfg(any(feature = "ptp", feature = "async-await"))] use core::task::Poll; -pub(crate) mod desc; - -pub(crate) mod ring; +pub(crate) mod generic_ring; mod rx; -pub use rx::{RunningState as RxRunningState, RxError, RxPacket, RxRing, RxRingEntry}; +pub use rx::{ + RunningState as RxRunningState, RxDescriptor, RxDescriptorRing, RxError, RxPacket, RxRing, +}; mod tx; -pub use tx::{RunningState as TxRunningState, TxError, TxPacket, TxRing, TxRingEntry}; +pub use tx::{ + RunningState as TxRunningState, TxDescriptor, TxDescriptorRing, TxError, TxPacket, TxRing, +}; #[cfg(feature = "ptp")] use crate::ptp::Timestamp; @@ -31,8 +36,29 @@ use crate::ptp::Timestamp; mod packet_id; pub use packet_id::PacketId; +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 _ASSERT_DESCRIPTOR_ALIGN: () = assert!(_RXDESC_SIZE % 4 == 0); + +const DESC_WORD_SKIP: u8 = ((_RXDESC_SIZE / 4) - self::generic_ring::DESC_SIZE) as u8; + +const _ASSERT_DESC_WORD_SKIP_SIZE: () = assert!(DESC_WORD_SKIP <= 0b111); + /// From the datasheet: *VLAN Frame maxsize = 1522* -pub(crate) const MTU: usize = 1522; +pub const MTU: usize = 1522; + +pub(crate) struct DmaParts { + pub eth_dma: ETHERNET_DMA, + #[cfg(feature = "stm32h7xx-hal")] + pub eth_mtl: crate::stm32::ETHERNET_MTL, +} #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Clone, Copy, Debug, PartialEq)] @@ -42,15 +68,19 @@ pub struct PacketIdNotFound; /// 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>, + tx_ring: TxRing<'tx>, #[cfg(feature = "ptp")] packet_id_counter: u32, } impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { + fn eth_dma(&self) -> ÐERNET_DMA { + &self.parts.eth_dma + } + /// Create and initialise the ethernet DMA /// /// # Note @@ -58,69 +88,137 @@ 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() - }); + let DmaParts { + eth_dma, + #[cfg(feature = "stm32h7xx-hal")] + eth_mtl, + } = &parts; - // 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(); + #[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() + // 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() + .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)); + } - unsafe { - // Address-aligned beats - w.aab() + #[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() - // 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() + // 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() + }); + } let mut dma = EthernetDMA { - eth_dma, + parts, rx_ring: RxRing::new(rx_buffer), tx_ring: TxRing::new(tx_buffer), @@ -128,8 +226,8 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { packet_id_counter: 0, }; - dma.rx_ring.start(&dma.eth_dma); - dma.tx_ring.start(&dma.eth_dma); + dma.rx_ring.start(&dma.parts.eth_dma); + dma.tx_ring.start(&dma.parts.eth_dma); dma } @@ -152,7 +250,8 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { doc = "If you have PTP enabled, you must also call [`EthernetPTP::interrupt_handler()`] if you wish to make use of the PTP timestamp trigger feature." )] 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() @@ -165,28 +264,117 @@ 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); } } + #[cfg(feature = "stm32h7xx-hal")] + fn panic_fbe() -> ! { + // SAFETY: we only perform atomic reads/writes through `eth_dma`. + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + let tx_descriptor_addr = eth_dma.dmaccatx_dr.read().bits(); + let tx_buffer_addr = eth_dma.dmaccatx_br.read().bits(); + + let rx_descriptor_addr = eth_dma.dmaccarx_dr.read().bits(); + let rx_buffer_addr = eth_dma.dmaccarx_br.read().bits(); + + // 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? TXDESC: {:08X}, TXBUF: {:08X}, RXDESC: {:08X}, TXDESC: {:08X}", tx_descriptor_addr, tx_buffer_addr, rx_descriptor_addr, rx_buffer_addr); + } + /// Handle the DMA parts of the `ETH` interrupt. - pub fn interrupt_handler() -> InterruptReasonSummary { + pub(crate) fn interrupt_handler() -> InterruptReasonSummary { // SAFETY: we only perform atomic reads/writes through `eth_dma`. let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - 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(); - let status = InterruptReasonSummary { - is_rx: status.rs().bit_is_set(), - is_tx: status.ts().bit_is_set(), - is_error: status.ais().bit_is_set(), + // Reset bits + eth_dma.dmasr.write(|w| { + w.nis() + .set_bit() + .ts() + .set_bit() + .rs() + .set_bit() + .ais() + .set_bit() + }); + + ( + 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() { + EthernetDMA::panic_fbe(); + } + + ( + status.ri().bit_is_set() || status.rbu().bit_is_set(), + status.ti().bit_is_set() || status.tbu().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, + }; #[cfg(feature = "async-await")] { @@ -216,12 +404,12 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { /// It stops if the ring is full. Call [`EthernetDMA::recv_next()`] to free an /// entry and to demand poll from the hardware. pub fn rx_is_running(&self) -> bool { - self.rx_ring.running_state().is_running() + RxRing::running_state().is_running() } /// Is Tx DMA currently running? pub fn tx_is_running(&self) -> bool { - self.tx_ring.is_running() + TxRing::is_running() } /// Try to send a packet with data. @@ -240,6 +428,7 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { let mut tx_packet = self.tx_ring.send_next(length, packet_id)?; f(&mut tx_packet); tx_packet.send(); + Ok(()) } @@ -263,9 +452,8 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { impl Drop for EthernetDMA<'_, '_> { // On drop, stop all DMA actions. fn drop(&mut self) { - self.tx_ring.stop(&self.eth_dma); - - self.rx_ring.stop(&self.eth_dma); + self.tx_ring.stop(self.eth_dma()); + self.rx_ring.stop(self.eth_dma()); } } @@ -291,11 +479,7 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { /// Prepare a packet for sending. /// /// See [`TxRing::prepare_packet`]. - pub async fn prepare_packet<'borrow>( - &'borrow mut self, - length: usize, - packet_id: Option, - ) -> TxPacket<'borrow, 'tx> { + pub async fn prepare_packet(&mut self, length: usize, packet_id: Option) -> TxPacket { self.tx_ring.prepare_packet(length, packet_id).await } diff --git a/src/dma/packet_id.rs b/src/dma/packet_id.rs index da85d8a..01283f4 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::poll_timest " )] #[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/ring.rs b/src/dma/ring.rs deleted file mode 100644 index 6076f24..0000000 --- a/src/dma/ring.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::{rx::RxDescriptor, tx::TxDescriptor, MTU}; - -pub trait RingDescriptor { - fn setup(&mut self, buffer: *const u8, len: usize, next: Option<&Self>); -} - -#[repr(C, align(8))] -pub struct Buffer { - buffer: [u8; MTU], -} - -impl Buffer { - pub const fn new() -> Self { - Self { buffer: [0; MTU] } - } -} - -impl core::ops::Deref for Buffer { - type Target = [u8; MTU]; - - fn deref(&self) -> &Self::Target { - &self.buffer - } -} - -impl core::ops::DerefMut for Buffer { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.buffer - } -} - -/// An entry in a DMA Descriptor ring -#[repr(C, align(8))] -pub struct RingEntry { - desc: T, - buffer: Buffer, -} - -impl Default for RingEntry { - fn default() -> Self { - RingEntry { - desc: T::default(), - buffer: Buffer::new(), - } - } -} - -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: TxDescriptor::new(), - buffer: Buffer::new(), - } - } -} - -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: RxDescriptor::new(), - buffer: Buffer::new(), - } - } -} - -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 - } - - #[inline] - pub(crate) fn desc_mut(&mut self) -> &mut T { - &mut self.desc - } - - #[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/descriptor.rs b/src/dma/rx/descriptor.rs deleted file mode 100644 index c3ae02b..0000000 --- a/src/dma/rx/descriptor.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::dma::{ - desc::Descriptor, - ring::{RingDescriptor, RingEntry}, -}; - -use crate::dma::PacketId; - -#[cfg(feature = "ptp")] -use crate::ptp::Timestamp; - -/// Errors that can occur during RX -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, PartialEq)] -pub(crate) enum RxDescriptorError { - /// The received packet was truncated - Truncated, - /// An error occured with the DMA - DmaError, -} - -/// RX timestamp valid -/// NOTE(allow): unused if not(feature = "ptp") -#[allow(unused)] -const RXDESC_0_TIMESTAMP_VALID: u32 = 1 << 7; -/// 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)] -/// An RX DMA Descriptor -pub struct RxDescriptor { - desc: Descriptor, - buffer1: Option, - next_descriptor: Option, - packet_id: Option, - #[cfg(feature = "ptp")] - cached_timestamp: Option, -} - -impl Default for RxDescriptor { - fn default() -> Self { - Self::new() - } -} - -impl RxDescriptor { - /// Creates an zeroed RxDescriptor. - pub const fn new() -> Self { - Self { - desc: Descriptor::new(), - buffer1: None, - next_descriptor: None, - packet_id: None, - #[cfg(feature = "ptp")] - cached_timestamp: 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 - pub fn set_owned(&mut self) { - self.write_buffer1(); - self.write_buffer2(); - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release); - - unsafe { - self.desc.write(0, RXDESC_0_OWN); - } - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - #[cfg(feature = "fence")] - core::sync::atomic::fence(core::sync::atomic::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 = { 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 - .buffer1 - .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.buffer1 = 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(); - - pub(super) fn is_available(&self) -> bool { - !self.desc().is_owned() - } - - /// Only call this if [`RxRingEntry::is_available`] - pub(super) fn recv(&mut self, packet_id: Option) -> Result { - if self.desc().has_error() { - self.desc_mut().set_owned(); - Err(RxDescriptorError::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." - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Acquire); - - #[cfg(feature = "ptp")] - { - // Cache the PTP timestamp - self.desc_mut().cached_timestamp = self.desc().timestamp(); - } - - // Set the Packet ID for this descriptor. - self.desc_mut().packet_id = packet_id; - - Ok(frame_len) - } else { - self.desc_mut().set_owned(); - Err(RxDescriptorError::Truncated) - } - } -} - -#[cfg(feature = "ptp")] -impl RxRingEntry { - pub fn has_packet_id(&self, id: &PacketId) -> bool { - Some(id) == self.desc().packet_id.as_ref() - } - - pub fn read_timestamp(&self) -> Option { - self.desc().cached_timestamp.clone() - } -} diff --git a/src/dma/rx/f_series_descriptor.rs b/src/dma/rx/f_series_descriptor.rs new file mode 100644 index 0000000..19d9ace --- /dev/null +++ b/src/dma/rx/f_series_descriptor.rs @@ -0,0 +1,193 @@ +use crate::dma::generic_ring::RawDescriptor; +use crate::dma::Cache; + +use crate::dma::{rx::RxDescriptorError, PacketId}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +/// RX timestamp valid +/// NOTE(allow): unused if not(feature = "ptp") +#[allow(unused)] +const RXDESC_0_TIMESTAMP_VALID: u32 = 1 << 7; +/// 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 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)] +/// An RX DMA Descriptor +#[derive(Clone, Copy)] +pub struct RxDescriptor { + desc: RawDescriptor, + cache: Cache, + last: bool, +} + +impl Default for RxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl RxDescriptor { + /// Creates an zeroed RxDescriptor. + pub const fn new() -> Self { + Self { + desc: RawDescriptor::new(), + last: false, + cache: Cache::new(), + } + } + + pub(super) fn setup(&mut self, end_of_ring: bool, buffer: &mut [u8]) { + self.last = end_of_ring; + self.set_owned(buffer); + } + + /// Is owned by the DMA engine? + fn is_owned(&self) -> bool { + (self.desc.read(0) & RXDESC_0_OWN) == RXDESC_0_OWN + } + + pub(super) fn is_available(&self) -> bool { + !self.is_owned() + } + + /// Pass ownership to the DMA engine + pub 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")] + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release); + + unsafe { + self.desc.write(0, RXDESC_0_OWN); + } + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + #[cfg(feature = "fence")] + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + } + + /// 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 { + let mut w = (buffer_len << RXDESC_1_RBS2_SHIFT) as u32 & RXDESC_1_RBS2_MASK; + + if self.last { + w |= RXDESC_1_RER; + } + + self.desc.write(1, w); + + self.desc.write(3, buffer_ptr as u32); + } + } + + /// Only call this if [`RxRingEntry::is_available`] + pub(super) fn recv( + &mut self, + packet_id: Option, + buffer: &mut [u8], + ) -> Result<(), RxDescriptorError> { + if self.has_error() { + self.set_owned(buffer); + Err(RxDescriptorError::DmaError) + } else if self.is_first() && self.is_last() { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Acquire); + + // Set the Packet ID for this descriptor. + self.cache.set_id_and_clear_ts(packet_id); + + #[cfg(feature = "ptp")] + self.cache.set_ts(self.read_timestamp()); + + Ok(()) + } else { + self.set_owned(buffer); + Err(RxDescriptorError::Truncated) + } + } + + 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 + } + + pub(super) fn frame_len(&self) -> usize { + ((self.desc.read(0) >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize + } +} + +#[cfg(feature = "ptp")] +impl RxDescriptor { + pub(super) fn has_packet_id(&self, id: &PacketId) -> bool { + Some(id) == self.cache.id().as_ref() + } + + fn read_timestamp(&self) -> Option { + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] + let (high, low) = { (self.desc.read(7), self.desc.read(6)) }; + + #[cfg(feature = "stm32f1xx-hal")] + let (high, low) = { (self.desc.read(3), self.desc.read(2)) }; + + #[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 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 + } + } + + /// Get PTP timestamp if available + pub(super) fn timestamp(&self) -> Option { + self.cache.ts() + } +} diff --git a/src/dma/rx/h_descriptor.rs b/src/dma/rx/h_descriptor.rs new file mode 100644 index 0000000..96b5a25 --- /dev/null +++ b/src/dma/rx/h_descriptor.rs @@ -0,0 +1,231 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{generic_ring::RawDescriptor, Cache, PacketId}; + +#[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::*; + +use super::RxDescriptorError; + +#[repr(C)] +#[repr(align(4))] +#[derive(Clone, Copy)] +/// An RX DMA Descriptor. +pub struct RxDescriptor { + inner_raw: RawDescriptor, + cache: Cache, +} + +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(), + cache: Cache::new(), + } + } + + /// Is owned by the DMA engine? + pub(super) fn is_owned(&self) -> bool { + (self.inner_raw.read(3) & RXDESC_3_OWN) == RXDESC_3_OWN + } + + pub(super) fn is_available(&self) -> bool { + !self.is_owned() + } + + 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 + } + + pub(super) fn is_context(&self) -> bool { + self.inner_raw.read(3) & RXDESC_3_CTXT == RXDESC_3_CTXT && self.is_available() + } + + pub(super) fn frame_len(&self) -> usize { + if self.is_owned() { + 0 + } else { + ((self.inner_raw.read(3) & RXDESC_3_PL_MASK) >> RXDESC_3_PL_SHIFT) as usize + } + } + + // We ignore the end_of_ring bool, but need it for compatibility with the F-series + // descriptor. + pub(super) fn setup(&mut self, _end_of_ring: bool, 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 [`RxRing`](super::RxRing) + + self.inner_raw.write(3, RXDESC_3_BUF1V); + } + } + + pub(super) fn recv( + &mut self, + packet_id: Option, + buffer: &mut [u8], + ) -> Result<(), RxDescriptorError> { + if self.has_error() { + Err(RxDescriptorError::DmaError) + } 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.cache.set_id_and_clear_ts(packet_id); + + Ok(()) + } else { + self.set_owned(buffer); + Err(RxDescriptorError::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 has_packet_id(&self, packet_id: &PacketId) -> bool { + self.cache.id().as_ref() == Some(packet_id) + } + + pub(super) fn attach_timestamp(&mut self, timestamp: Option) { + self.cache.set_ts(timestamp); + } + + pub(super) fn timestamp(&self) -> Option { + self.cache.ts() + } +} diff --git a/src/dma/rx/mod.rs b/src/dma/rx/mod.rs index 7d59b5e..3dbcab3 100644 --- a/src/dma/rx/mod.rs +++ b/src/dma/rx/mod.rs @@ -1,11 +1,14 @@ -pub(crate) use self::descriptor::RxDescriptor; +pub use descriptor::RxDescriptor; -use self::descriptor::RxDescriptorError; -pub use self::descriptor::RxRingEntry; - -use super::PacketId; +use super::{generic_ring::DescriptorRing, PacketId}; use crate::peripherals::ETHERNET_DMA; +#[cfg(feature = "f-series")] +#[path = "./f_series_descriptor.rs"] +mod descriptor; + +#[cfg(feature = "stm32h7xx-hal")] +#[path = "./h_descriptor.rs"] mod descriptor; #[cfg(feature = "ptp")] @@ -14,6 +17,16 @@ use crate::{dma::PacketIdNotFound, ptp::Timestamp}; #[cfg(feature = "async-await")] use core::task::Poll; +/// Errors that can occur during RX +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq)] +pub(crate) enum RxDescriptorError { + /// The received packet was truncated + Truncated, + /// An error occured with the DMA + DmaError, +} + /// Errors that can occur during RX #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, PartialEq)] @@ -35,17 +48,20 @@ impl From for RxError { } } +/// An RX descriptor ring. +pub type RxDescriptorRing<'rx> = DescriptorRing<'rx, RxDescriptor>; + /// Rx DMA state pub struct RxRing<'a> { - entries: &'a mut [RxRingEntry], + ring: RxDescriptorRing<'a>, next_entry: usize, } impl<'a> RxRing<'a> { /// Allocate - pub(crate) fn new(entries: &'a mut [RxRingEntry]) -> Self { + pub(crate) fn new(ring: RxDescriptorRing<'a>) -> Self { RxRing { - entries, + ring, next_entry: 0, } } @@ -53,57 +69,129 @@ impl<'a> RxRing<'a> { /// Setup the DMA engine (**required**) pub(crate) 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); - } + let ring_len = self.ring.len(); + for (idx, (entry, buffer)) in self.ring.descriptors_and_buffers().enumerate() { + entry.setup(idx == ring_len - 1, buffer); } + self.next_entry = 0; - let ring_ptr = self.entries[0].desc() as *const RxDescriptor; + let ring_ptr = self.ring.descriptors_start_address(); - // Register RxDescriptor - eth_dma - .dmardlar - .write(|w| unsafe { w.srl().bits(ring_ptr as u32) }); + #[cfg(feature = "f-series")] + { + // Set the RxDma ring start address. + 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()); + } - // 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() + }); + } - self.demand_poll(); + Self::demand_poll(); } /// Stop the RX DMA pub(crate) fn stop(&self, eth_dma: ÐERNET_DMA) { - eth_dma.dmaomr.modify(|_, w| w.sr().clear_bit()); + #[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()); // DMA accesses do not stop before the running state // of the DMA has changed to something other than // running. - while self.running_state().is_running() {} + while Self::running_state().is_running() {} } /// Demand that the DMA engine polls the current `RxDescriptor` /// (when in [`RunningState::Stopped`].) - fn demand_poll(&self) { - // SAFETY: we only perform an atomic write to `dmarpdr`. + fn demand_poll() { + // # 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()) }); } - /// Get current `RunningState` - pub fn running_state(&self) -> RunningState { + /// Get current state of the RxDMA + pub fn running_state() -> RunningState { // SAFETY: we only perform an atomic read of `dmasr`. let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - match eth_dma.dmasr.read().rps().bits() { + + #[cfg(feature = "stm32h7xx-hal")] + if eth_dma.dmacsr.read().fbe().bit_is_set() { + super::EthernetDMA::panic_fbe(); + } + + #[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 @@ -116,56 +204,90 @@ impl<'a> RxRing<'a> { 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, } } /// 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].is_available() + self.ring.descriptor(self.next_entry).is_available() } - /// Receive the next packet (if any is ready). + /// Obtain the index of the packet to receive (if any is ready). /// - /// This function returns a tuple of `Ok((entry_index, length))` on + /// This function returns a tuple of `Ok(entry_index)` on /// success. Whoever receives the `Ok` must ensure that `set_owned` /// is eventually called on the entry with that index. + /// + /// Actually obtaining the relevant RxPacket is done using + /// [`RxRing::recv_and_timestamp`]. fn recv_next_impl( &mut self, // NOTE(allow): packet_id is unused if ptp is disabled. #[allow(unused_variables)] packet_id: Option, - ) -> Result<(usize, usize), RxError> { - if !self.running_state().is_running() { - self.demand_poll(); + ) -> Result { + if !Self::running_state().is_running() { + Self::demand_poll(); } - let entries_len = self.entries.len(); - let entry_num = self.next_entry; - let entry = &mut self.entries[entry_num]; + if self.next_entry_available() { + let entries_len = self.ring.len(); + let (desc, buffer) = self.ring.get_mut(self.next_entry); - if entry.is_available() { - let length = entry.recv(packet_id)?; + desc.recv(packet_id, buffer)?; + let entry_num = self.next_entry; self.next_entry = (self.next_entry + 1) % entries_len; - Ok((entry_num, length)) + Ok(entry_num) } else { Err(RxError::WouldBlock) } } + fn recv_and_timestamp(&mut self, entry: usize) -> RxPacket { + #[cfg(feature = "stm32h7xx-hal")] + let entries_len = self.ring.len(); + + #[cfg(feature = "f-series")] + let (desc, buffer) = self.ring.get_mut(entry); + + #[cfg(feature = "stm32h7xx-hal")] + let (desc, buffer) = { + let (desc, buffer, next_desc, next_buffer) = self.ring.get_mut_and_next(entry); + + // Read the timestamp from the next context descriptor, if it's available. + if next_desc.is_context() { + #[cfg(feature = "ptp")] + if desc.has_timestamp() { + let timestamp = next_desc.read_timestamp(); + desc.attach_timestamp(timestamp); + } + next_desc.set_owned(next_buffer); + self.next_entry = (self.next_entry + 1) % entries_len; + } + + (desc, buffer) + }; + + let length = desc.frame_len(); + + RxPacket { + entry: desc, + buffer, + length, + } + } + /// Receive the next packet (if any is ready), or return [`Err`] /// immediately. pub fn recv_next(&mut self, packet_id: Option) -> Result { - let (entry, length) = self.recv_next_impl(packet_id.map(|p| p.into()))?; - Ok(RxPacket { - entry: &mut self.entries[entry], - length, - }) + let entry = self.recv_next_impl(packet_id)?; + + Ok(self.recv_and_timestamp(entry)) } /// Receive the next packet. @@ -174,7 +296,7 @@ impl<'a> RxRing<'a> { /// will contain the ethernet data. #[cfg(feature = "async-await")] pub async fn recv(&mut self, packet_id: Option) -> RxPacket { - let (entry, length) = core::future::poll_fn(|ctx| { + let entry = core::future::poll_fn(|ctx| { let res = self.recv_next_impl(packet_id.clone()); match res { @@ -187,10 +309,7 @@ impl<'a> RxRing<'a> { }) .await; - RxPacket { - entry: &mut self.entries[entry], - length, - } + self.recv_and_timestamp(entry) } } @@ -198,15 +317,16 @@ impl<'a> RxRing<'a> { impl<'a> RxRing<'a> { /// Get the timestamp for a specific ID pub fn timestamp(&self, id: &PacketId) -> Result, PacketIdNotFound> { - let entry = self.entries.iter().find(|e| e.has_packet_id(id)); + let entry = self.ring.descriptors().find(|e| e.has_packet_id(id)); let entry = entry.ok_or(PacketIdNotFound)?; - Ok(entry.read_timestamp()) + Ok(entry.timestamp()) } } /// Running state of the `RxRing` +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(PartialEq, Eq, Debug)] pub enum RunningState { /// Running state is unknown. @@ -228,32 +348,33 @@ impl RunningState { /// /// This packet implements [Deref<\[u8\]>](core::ops::Deref) and should be used /// as a slice. -pub struct RxPacket<'a> { - entry: &'a mut RxRingEntry, +pub struct RxPacket<'a, 'buf> { + entry: &'a mut RxDescriptor, + buffer: &'buf mut [u8], length: usize, } -impl<'a> core::ops::Deref for RxPacket<'a> { +impl core::ops::Deref for RxPacket<'_, '_> { type Target = [u8]; fn deref(&self) -> &Self::Target { - &self.entry.as_slice()[0..self.length] + &self.buffer[..self.length] } } -impl<'a> core::ops::DerefMut for RxPacket<'a> { +impl<'a> core::ops::DerefMut for RxPacket<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.entry.as_mut_slice()[0..self.length] + &mut self.buffer[..self.length] } } -impl<'a> Drop for RxPacket<'a> { +impl Drop for RxPacket<'_, '_> { fn drop(&mut self) { - self.entry.desc_mut().set_owned(); + self.entry.set_owned(self.buffer); } } -impl<'a> RxPacket<'a> { +impl RxPacket<'_, '_> { /// Pass the received packet back to the DMA engine. pub fn free(self) { drop(self) @@ -262,6 +383,6 @@ impl<'a> RxPacket<'a> { /// Get the timestamp associated with this packet #[cfg(feature = "ptp")] pub fn timestamp(&self) -> Option { - self.entry.read_timestamp() + self.entry.timestamp() } } diff --git a/src/dma/tx/descriptor.rs b/src/dma/tx/descriptor.rs deleted file mode 100644 index 9468f8e..0000000 --- a/src/dma/tx/descriptor.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::dma::{ - desc::Descriptor, - ring::{RingDescriptor, RingEntry}, - PacketId, -}; - -#[cfg(feature = "ptp")] -use crate::ptp::Timestamp; - -/// 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; - -/// A TX DMA Ring Descriptor -#[repr(C)] -pub struct TxDescriptor { - desc: Descriptor, - packet_id: Option, - buffer1: u32, - next_descriptor: u32, - is_last: bool, -} - -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, - buffer1: 0, - next_descriptor: 0, - is_last: false, - } - } - - #[allow(unused)] - fn has_error(&self) -> bool { - (self.desc.read(0) & TXDESC_0_ES) == TXDESC_0_ES - } - - /// Is owned by the DMA engine? - 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(&self) -> bool { - self.desc.read(0) & TXDESC_0_LS == TXDESC_0_LS - } - - /// Pass ownership to the DMA engine - fn set_owned(&mut self, length: usize, packet_id: Option) { - // Reconfigure packet ID - self.packet_id = packet_id; - - self.set_buffer1_len(length); - - // These descriptor values are sometimes overwritten by - // timestamp data, so we rewrite this data. - let buffer1 = self.buffer1; - unsafe { - self.desc.write(2, buffer1); - } - - let buffer2 = self.next_descriptor; - unsafe { - self.desc.write(3, buffer2); - } - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release); - - let mut extra_flags = 0; - - if self.packet_id.is_some() { - extra_flags |= TXDESC_0_TIMESTAMP_ENABLE; - } - - if self.is_last { - extra_flags |= TXDESC_0_TER; - } - - unsafe { - self.desc.write( - 0, - TXDESC_0_OWN - | TXDESC_0_TCH - | TXDESC_0_FS - | TXDESC_0_LS - | TXDESC_0_CIC0 - | TXDESC_0_CIC1 - | TXDESC_0_IC - | extra_flags, - ) - } - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - #[cfg(feature = "fence")] - core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); - } - - fn set_buffer1_len(&mut self, len: usize) { - unsafe { - self.desc.modify(1, |w| { - let masked_len = w & !TXDESC_1_TBS_MASK; - let with_len = masked_len | ((len as u32) << TXDESC_1_TBS_SHIFT); - with_len - }); - } - } - - #[cfg(feature = "ptp")] - fn timestamp(&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() { - 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>) { - unsafe { - self.desc.clear(); - } - - // Defer this initialization to this function, so we can have `RingEntry` on bss. - let next_desc_addr = if let Some(next) = next { - &next.desc as *const Descriptor as *const u8 as u32 - } else { - self.is_last = true; - 0 - }; - - self.buffer1 = buffer as u32; - self.next_descriptor = next_desc_addr; - } -} - -impl TxRingEntry { - pub(super) fn is_available(&self) -> bool { - !self.desc().is_owned() - } - - /// Only call this if [`TxRingEntry::is_available`] - pub(super) fn send(&mut self, length: usize, packet_id: Option) { - self.desc_mut().set_owned(length, packet_id); - } - - /// Only call this if [`TxRingEntry::is_available`] - pub fn buffer(&self) -> &[u8] { - self.as_slice() - } - - /// Only call this if [`TxRingEntry::is_available`] - pub fn buffer_mut(&mut self) -> &mut [u8] { - self.as_mut_slice() - } -} - -#[cfg(feature = "ptp")] -impl TxRingEntry { - pub fn has_packet_id(&self, packet_id: &PacketId) -> bool { - self.desc().packet_id.as_ref() == Some(packet_id) - } - - pub fn timestamp(&self) -> Option { - self.desc().timestamp().clone() - } -} diff --git a/src/dma/tx/f_series_descriptor.rs b/src/dma/tx/f_series_descriptor.rs new file mode 100644 index 0000000..145ab96 --- /dev/null +++ b/src/dma/tx/f_series_descriptor.rs @@ -0,0 +1,182 @@ +use crate::dma::{generic_ring::RawDescriptor, Cache, PacketId}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +/// 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; +/// 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)] +#[derive(Clone, Copy)] +pub struct TxDescriptor { + desc: RawDescriptor, + last: bool, + cache: Cache, +} + +impl Default for TxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl TxDescriptor { + /// Creates an zeroed TxDescriptor. + pub const fn new() -> Self { + Self { + desc: RawDescriptor::new(), + last: false, + cache: Cache::new(), + } + } + + pub(super) fn setup(&mut self) { + (0..crate::dma::generic_ring::DESC_SIZE).for_each(|i| unsafe { self.desc.write(i, 0) }); + } + + #[allow(unused)] + pub(super) fn has_error(&self) -> bool { + (self.desc.read(0) & TXDESC_0_ES) == TXDESC_0_ES + } + + /// Is owned by the DMA engine? + pub(super) fn is_owned(&self) -> bool { + (self.desc.read(0) & TXDESC_0_OWN) == TXDESC_0_OWN + } + + pub(super) fn is_available(&self) -> bool { + !self.is_owned() + } + + // NOTE(allow): packet_id is unused if ptp is disabled. + #[allow(dead_code)] + pub(super) fn is_last(&self) -> bool { + self.desc.read(0) & TXDESC_0_LS == TXDESC_0_LS + } + + pub(crate) 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 + }; + + let extra_flags = if self.last { + extra_flags | TXDESC_0_TER + } else { + extra_flags + }; + + self.cache.set_id_and_clear_ts(packet_id); + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release); + + unsafe { + self.desc.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")] + core::sync::atomic::fence(core::sync::atomic::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.desc.write(3, ptr as u32); + + self.desc.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 + }); + } + } + + pub(crate) fn set_end_of_ring(&mut self) { + self.last = true; + } +} + +#[cfg(feature = "ptp")] +impl TxDescriptor { + pub(super) fn has_packet_id(&self, packet_id: &PacketId) -> bool { + self.cache.id().as_ref() == Some(packet_id) + } + + /// For the TxDescriptor we ignore [`Cache::ts`] because: + /// * We're only really using the cache so that the size of RxDescriptor and TxDescriptor + /// is the same. + /// * We want to be able to retrieve the timestamp immutably. + /// * The Timestamp in the TX descriptor is valid until we perform another transmission. + pub(super) fn timestamp(&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() { + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] + let (high, low) = { (self.desc.read(7), self.desc.read(6)) }; + + #[cfg(feature = "stm32f1xx-hal")] + let (high, low) = { (self.desc.read(3), self.desc.read(2)) }; + + Some(Timestamp::from_parts(high, low)) + } else { + None + } + } +} diff --git a/src/dma/tx/h_descriptor.rs b/src/dma/tx/h_descriptor.rs new file mode 100644 index 0000000..7460d9a --- /dev/null +++ b/src/dma/tx/h_descriptor.rs @@ -0,0 +1,247 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{generic_ring::RawDescriptor, Cache, 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, + cache: Cache, +} + +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(), + cache: Cache::new(), + } + } + + #[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.cache.set_id_and_clear_ts(None); + } + + pub(super) fn is_owned(&self) -> bool { + (self.inner_raw.read(3) & TXDESC_3_OWN) == TXDESC_3_OWN + } + + pub(super) fn is_available(&self) -> bool { + !self.is_owned() + } + + #[allow(unused)] + pub(super) fn is_context(&self) -> bool { + (self.inner_raw.read(3) & TXDESC_3_CTXT) == TXDESC_3_CTXT + } + + /// 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.cache.set_id_and_clear_ts(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); + // Set buffer pointer 2 to NULL + self.inner_raw.write(1, 0); + + 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 { + pub(super) fn has_packet_id(&self, packet_id: &PacketId) -> bool { + self.cache.id().as_ref() == Some(packet_id) + } + + /// For the TxDescriptor we ignore [`Cache::ts`] because: + /// * We're only really using the cache so that the size of RxDescriptor and TxDescriptor + /// is the same. + /// * We want to be able to retrieve the timestamp immutably. + /// * The Timestamp in the TX descriptor is valid until we perform another transmission. + pub(super) fn timestamp(&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 + } + } +} diff --git a/src/dma/tx/mod.rs b/src/dma/tx/mod.rs index 8d0d6a4..0663d57 100644 --- a/src/dma/tx/mod.rs +++ b/src/dma/tx/mod.rs @@ -1,15 +1,25 @@ -use super::PacketId; +use super::{generic_ring::DescriptorRing, PacketId}; use crate::peripherals::ETHERNET_DMA; #[cfg(feature = "ptp")] use super::{PacketIdNotFound, Timestamp}; +#[cfg(feature = "f-series")] +#[path = "./f_series_descriptor.rs"] mod descriptor; -pub use descriptor::{TxDescriptor, TxRingEntry}; + +#[cfg(feature = "stm32h7xx-hal")] +#[path = "./h_descriptor.rs"] +mod descriptor; + +pub use descriptor::TxDescriptor; #[cfg(any(feature = "ptp", feature = "async-await"))] use core::task::Poll; +/// A TX descriptor ring. +pub type TxDescriptorRing<'rx> = DescriptorRing<'rx, TxDescriptor>; + /// Errors that can occur during Ethernet TX #[derive(Debug, PartialEq)] pub enum TxError { @@ -19,7 +29,7 @@ pub enum TxError { /// Tx DMA state pub struct TxRing<'a> { - entries: &'a mut [TxRingEntry], + ring: TxDescriptorRing<'a>, next_entry: usize, } @@ -27,35 +37,61 @@ impl<'ring> TxRing<'ring> { /// Allocate /// /// `start()` will be needed before `send()` - pub(crate) fn new(entries: &'ring mut [TxRingEntry]) -> Self { + pub(crate) fn new(ring: TxDescriptorRing<'ring>) -> Self { TxRing { - entries, + ring, next_entry: 0, } } /// Start the Tx DMA engine pub(crate) fn start(&mut self, eth_dma: ÐERNET_DMA) { - // Setup ring + for descriptor in self.ring.descriptors_mut() { + descriptor.setup(); + } + + let ring_ptr = self.ring.descriptors_start_address(); + + #[cfg(feature = "f-series")] { - 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); - } + // Set end of ring register + self.ring.last_descriptor_mut().set_end_of_ring(); + + // Register TxDescriptor + eth_dma + .dmatdlar + // Note: unsafe block required for `stm32f107`. + .write(|w| unsafe { w.stl().bits(ring_ptr as u32) }); } - 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) }); + #[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")] @@ -64,23 +100,37 @@ impl<'ring> TxRing<'ring> { // 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 - eth_dma.dmaomr.modify(|_, w| w.st().set_bit()); + start_reg.modify(|_, w| w.st().set_bit()); } /// Stop the TX DMA pub(crate) fn stop(&self, eth_dma: ÐERNET_DMA) { - eth_dma.dmaomr.modify(|_, w| w.st().clear_bit()); + #[cfg(feature = "f-series")] + let start_reg = ð_dma.dmaomr; + #[cfg(feature = "stm32h7xx-hal")] + let start_reg = ð_dma.dmactx_cr; + + start_reg.modify(|_, w| w.st().clear_bit()); // DMA accesses do not stop before the running state // of the DMA has changed to something other than // running. - while self.is_running() {} + while Self::is_running() {} + } + + fn entry_available(&self, index: usize) -> bool { + self.ring.descriptor(index).is_available() } /// If this returns `true`, the next `send` will succeed. pub fn next_entry_available(&self) -> bool { - self.entries[self.next_entry].is_available() + self.entry_available(self.next_entry % self.ring.len()) } /// Check if we can send the next TX entry. @@ -89,12 +139,10 @@ impl<'ring> TxRing<'ring> { /// that [`self.entries[res].send()`](TxRingEntry::send) is called /// before a new invocation of `send_next_impl`. fn send_next_impl(&mut self) -> Result { - let entries_len = self.entries.len(); - let entry_num = self.next_entry; - let entry = &mut self.entries[entry_num]; + if self.next_entry_available() { + let entry_num = self.next_entry; - if entry.is_available() { - self.next_entry = (self.next_entry + 1) % entries_len; + self.next_entry = (self.next_entry + 1) % self.ring.len(); Ok(entry_num) } else { Err(TxError::WouldBlock) @@ -109,19 +157,20 @@ impl<'ring> TxRing<'ring> { /// /// When all data is copied into the TX buffer, use [`TxPacket::send()`] /// to transmit it. - pub fn send_next<'borrow>( - &'borrow mut self, + pub fn send_next( + &mut self, length: usize, packet_id: Option, - ) -> Result, TxError> { + ) -> Result { let entry = self.send_next_impl()?; - let tx_buffer = self.entries[entry].buffer_mut(); + + let (desc, tx_buffer) = self.ring.get_mut(entry); assert!(length <= tx_buffer.len(), "Not enough space in TX buffer"); Ok(TxPacket { - ring: self, - idx: entry, + desc, + buffer: tx_buffer, length, packet_id, }) @@ -136,11 +185,11 @@ impl<'ring> TxRing<'ring> { /// When all data is copied into the TX buffer, use [`TxPacket::send()`] /// to transmit it. #[cfg(feature = "async-await")] - pub async fn prepare_packet<'borrow>( - &'borrow mut self, + pub async fn prepare_packet<'tx>( + &'tx mut self, length: usize, packet_id: Option, - ) -> TxPacket<'borrow, 'ring> { + ) -> TxPacket { let entry = core::future::poll_fn(|ctx| match self.send_next_impl() { Ok(packet) => Poll::Ready(packet), Err(_) => { @@ -150,12 +199,13 @@ impl<'ring> TxRing<'ring> { }) .await; - let tx_buffer = self.entries[entry].buffer_mut(); + let (desc, tx_buffer) = self.ring.get_mut(entry); + assert!(length <= tx_buffer.len(), "Not enough space in TX buffer"); TxPacket { - ring: self, - idx: entry, + desc, + buffer: tx_buffer, length, packet_id, } @@ -163,9 +213,27 @@ impl<'ring> TxRing<'ring> { /// Demand that the DMA engine polls the current `TxDescriptor` /// (when we just transferred ownership to the hardware). - pub(crate) fn demand_poll(&self) { - // SAFETY: we only perform an atomic write to `dmatpdr` + pub(crate) fn demand_poll() { + // # SAFETY + // + // On F-series, 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"))] { @@ -180,15 +248,28 @@ impl<'ring> TxRing<'ring> { } /// Is the Tx DMA engine running? - pub fn is_running(&self) -> bool { - self.running_state().is_running() + pub fn is_running() -> bool { + Self::running_state().is_running() } - pub(crate) fn running_state(&self) -> RunningState { - // SAFETY: we only perform an atomic read of `dmasr`. + /// Get the current state of the TxDMA + pub fn running_state() -> RunningState { + // SAFETY: we only perform an atomic read of `dmasr` or + // `dmadsr`. let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - match eth_dma.dmasr.read().tps().bits() { + #[cfg(feature = "stm32h7xx-hal")] + if eth_dma.dmacsr.read().fbe().bit_is_set() { + super::EthernetDMA::panic_fbe(); + } + + #[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 @@ -197,9 +278,22 @@ impl<'ring> TxRing<'ring> { 0b010 => RunningState::Running, // Reading Data from host memory buffer and queuing it to transmit buffer 0b011 => RunningState::Running, - 0b100 | 0b101 => RunningState::Reserved, + + 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, } } @@ -208,23 +302,17 @@ impl<'ring> TxRing<'ring> { #[cfg(feature = "ptp")] impl TxRing<'_> { fn entry_for_id(&self, id: &PacketId) -> Option { - self.entries.iter().enumerate().find_map( - |(idx, e)| { - if e.has_packet_id(id) { - Some(idx) - } else { - None - } - }, - ) - } - - fn entry_available(&self, index: usize) -> bool { - self.entries[index].is_available() + self.ring.descriptors().enumerate().find_map(|(idx, e)| { + if e.has_packet_id(id) { + Some(idx) + } else { + None + } + }) } fn entry_timestamp(&self, index: usize) -> Option { - self.entries[index].timestamp() + self.ring.descriptor(index).timestamp() } /// Blockingly wait untill the timestamp for the @@ -277,6 +365,7 @@ impl TxRing<'_> { } #[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The run state of the TX DMA. pub enum RunningState { /// Reset or Stop Transmit Command issued @@ -308,37 +397,38 @@ impl RunningState { /// /// [`Deref`]: core::ops::Deref /// [`DerefMut`]: core::ops::DerefMut -pub struct TxPacket<'borrow, 'ring> { - ring: &'borrow mut TxRing<'ring>, - idx: usize, +pub struct TxPacket<'borrow> { + desc: &'borrow mut TxDescriptor, + buffer: &'borrow mut [u8], length: usize, packet_id: Option, } -impl core::ops::Deref for TxPacket<'_, '_> { +impl core::ops::Deref for TxPacket<'_> { type Target = [u8]; fn deref(&self) -> &Self::Target { - &self.ring.entries[self.idx].buffer()[..self.length] + &self.buffer[..self.length] } } -impl core::ops::DerefMut for TxPacket<'_, '_> { +impl core::ops::DerefMut for TxPacket<'_> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.ring.entries[self.idx].buffer_mut()[..self.length] + &mut self.buffer[..self.length] } } -impl TxPacket<'_, '_> { +impl TxPacket<'_> { /// Send this packet! pub fn send(self) { drop(self); } } -impl Drop for TxPacket<'_, '_> { +impl Drop for TxPacket<'_> { fn drop(&mut self) { - self.ring.entries[self.idx].send(self.length, self.packet_id.clone()); - self.ring.demand_poll(); + self.desc + .send(self.packet_id.clone(), &self.buffer[..self.length]); + TxRing::demand_poll(); } } diff --git a/src/lib.rs b/src/lib.rs index ed7da6b..a195ed8 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,9 +24,13 @@ 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; @@ -47,8 +54,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::*, }; @@ -113,8 +120,8 @@ pub fn eth_interrupt_handler() -> InterruptReason { #[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> @@ -133,17 +140,32 @@ 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, + }; // 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, + ); + + let mac_parts = MacParts { + eth_mac: parts.mac.into(), + #[cfg(feature = "f-series")] + eth_mmc: parts.mmc.into(), + }; // 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, @@ -178,8 +200,8 @@ where #[cfg(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, @@ -202,18 +224,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, + }; // 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, + ); + + let mac_parts = MacParts { + eth_mac: parts.mac.into(), + #[cfg(feature = "f-series")] + eth_mmc: parts.mmc.into(), + }; // 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 7e621fd..7012b77 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(all(feature = "stm32h7xx-hal", feature = "ptp"))] + // 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,39 +294,10 @@ impl EthernetMAC { MDIO: MdioPin, MDC: MdcPin, { - EthernetMACWithMii { - eth_mac: self, - mdio, - mdc, - } + EthernetMACWithMii::new(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, - } - } - - #[cfg(feature = "ptp")] + #[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() }; @@ -218,103 +305,10 @@ impl EthernetMAC { } // NOTE(allow): only used on F4 and F7 - #[allow(dead_code)] + #[cfg(all(feature = "ptp", feature = "f-series", not(feature = "stm32f1xx-hal")))] 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 ff2b9a7..5466fbe 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -7,6 +7,9 @@ pub use crate::hal::pac::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_PTP}; #[cfg(feature = "stm32f4xx-hal")] pub use pac_override_impl::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_PTP}; +#[cfg(feature = "stm32h7xx-hal")] +pub use crate::hal::pac::{ETHERNET_DMA, ETHERNET_MAC}; + #[cfg(feature = "stm32f4xx-hal")] mod pac_override_impl { #![allow(non_camel_case_types)] @@ -193,6 +196,7 @@ mod pac_override_impl { #[doc = r"Return the pointer to the register block"] #[inline(always)] + #[allow(unused)] pub const fn ptr() -> *const MacRegisterBlock { Self::PTR } @@ -257,6 +261,9 @@ mod pac_override_impl { #[doc = r"Pointer to the register block"] pub const PTR: *const DmaRegisterBlock = 0x4002_9000 as *const _; + #[doc = r"Return the pointer to the register block"] + #[inline(always)] + #[allow(unused)] pub const fn ptr() -> *const DmaRegisterBlock { Self::PTR } diff --git a/src/ptp/mod.rs b/src/ptp/mod.rs index 3d3d6d3..b550fed 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::peripherals::ETHERNET_PTP; mod timestamp; pub use timestamp::Timestamp; @@ -47,10 +50,19 @@ pub use pps_pin::PPSPin; /// /// [`NonZeroU8`]: core::num::NonZeroU8 pub struct EthernetPTP { + #[cfg(feature = "f-series")] eth_ptp: ETHERNET_PTP, } impl EthernetPTP { + #[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() -> &'static crate::stm32::ethernet_mac::RegisterBlock { + &*crate::peripherals::ETHERNET_MAC::ptr() + } + // Calculate the `addend` required for running `global_time` at // the correct rate const fn calculate_regs(hclk: u32) -> (Subseconds, u32) { @@ -69,35 +81,69 @@ impl EthernetPTP { } 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(); + crate::mac::EthernetMAC::mask_timestamp_trigger_interrupt(); let hclk = clocks.hclk().to_Hz(); 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) }); + Self { eth_ptp } + }; - let mut me = 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 { Self::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)); @@ -107,49 +153,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; - // 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() {} + 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() {} + } + + #[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. @@ -157,24 +258,45 @@ 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 + + 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() {} + } - 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 @@ -185,12 +307,30 @@ impl EthernetPTP { /// Get the current time. pub fn get_time() -> Timestamp { let try_read_time = || { - // SAFETY: we only atomically read registers. - let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() }; - - let seconds = eth_ptp.ptptshr.read().bits(); - let subseconds = eth_ptp.ptptslr.read().bits(); - let seconds_after = eth_ptp.ptptshr.read().bits(); + #[cfg(feature = "f-series")] + let (seconds, subseconds, seconds_after) = { + // SAFETY: we only atomically read registers. + let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() }; + + let seconds = eth_ptp.ptptshr.read().bits(); + let subseconds = eth_ptp.ptptslr.read().bits(); + let seconds2 = eth_ptp.ptptshr.read().bits(); + (seconds, subseconds, seconds2) + }; + + #[cfg(feature = "stm32h7xx-hal")] + let (seconds, subseconds, seconds_after) = { + // SAFETY: we only atomically read PTP registers. + 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)) @@ -229,12 +369,29 @@ 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. @@ -243,18 +400,22 @@ 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()); + crate::mac::EthernetMAC::unmask_timestamp_trigger_interrupt(); + } } /// Wait until the specified time. #[cfg(feature = "async-await")] pub async fn wait_until(&mut self, timestamp: Timestamp) { self.configure_target_time_interrupt(timestamp); + core::future::poll_fn(|ctx| { if EthernetPTP::read_and_clear_interrupt_flag() { Poll::Ready(()) - } else if EthernetPTP::get_time().raw() >= timestamp.raw() { + } else if EthernetPTP::now().raw() >= timestamp.raw() { Poll::Ready(()) } else { EthernetPTP::waker().register(ctx.waker()); @@ -266,8 +427,25 @@ impl EthernetPTP { #[inline(always)] fn read_and_clear_interrupt_flag() -> bool { - let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() }; - eth_ptp.ptptssr.read().tsttr().bit_is_set() + #[cfg(feature = "f-series")] + { + let eth_ptp = unsafe { &*ETHERNET_PTP::ptr() }; + eth_ptp.ptptssr.read().tsttr().bit_is_set() + } + #[cfg(feature = "stm32h7xx-hal")] + { + // SAFETY: we only read the ethernet ptp status register. + + let mac = unsafe { Self::mac() }; + + // Reading the register clears all of the bits in + // that register. + let tssr = mac.mactssr.read(); + let tstargt0 = tssr.tstargt0().bit_is_set(); + let tstrgterr0 = tssr.tstrgterr0().bit_is_set(); + + tstargt0 || tstrgterr0 + } } /// Handle the PTP parts of the `ETH` interrupt. @@ -276,19 +454,32 @@ impl EthernetPTP { /// was caused by a Timestamp trigger and clears the interrupt /// flag. pub fn interrupt_handler() -> bool { - // SAFETY: we only perform one atomic read. - let eth_mac = unsafe { &*crate::peripherals::ETHERNET_MAC::ptr() }; + #[cfg(feature = "f-series")] + let is_tsint = { + // SAFETY: we only perform one atomic read. + let eth_mac = unsafe { &*crate::peripherals::ETHERNET_MAC::ptr() }; + + let is_tsint = eth_mac.macsr.read().tsts().bit_is_set(); + if is_tsint { + crate::mac::EthernetMAC::mask_timestamp_trigger_interrupt(); + } + is_tsint + }; - let is_tsint = eth_mac.macsr.read().tsts().bit_is_set(); - if is_tsint { - EthernetMAC::mask_timestamp_trigger_interrupt(); - } + #[cfg(feature = "stm32h7xx-hal")] + let is_tsint = { + // SAFETY: we only read from `macisr` (Interrupt status register) + let macisr = unsafe { &Self::mac().macisr }; + macisr.read().tsis().bit_is_set() + }; #[cfg(feature = "async-await")] - if let Some(waker) = EthernetPTP::waker().take() { - waker.wake(); - } else { - EthernetPTP::read_and_clear_interrupt_flag(); + if is_tsint { + if let Some(waker) = EthernetPTP::waker().take() { + waker.wake(); + } else { + EthernetPTP::read_and_clear_interrupt_flag(); + } } #[cfg(not(feature = "async-await"))] @@ -307,10 +498,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)); + } } } 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 da088c2..db37863 100644 --- a/src/ptp/timestamp.rs +++ b/src/ptp/timestamp.rs @@ -1,5 +1,3 @@ -use crate::dma::desc::Descriptor; - use super::{Subseconds, NANOS_PER_SECOND}; /// A timestamp produced by the PTP periperhal @@ -118,28 +116,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..e61c41f 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -4,46 +4,74 @@ //! which pins can have a specific function, and provides //! functionality for setting up clocks and the MAC peripheral +use crate::{ + dma::EthernetDMA, + stm32::{ETHERNET_DMA, ETHERNET_MAC}, +}; + #[cfg(feature = "stm32f4xx-hal")] -use stm32f4xx_hal::{ - bb, +use crate::hal::bb; + +#[cfg(any( + feature = "stm32f4xx-hal", + feature = "stm32f7xx-hal", + feature = "stm32h7xx-hal" +))] +use crate::hal::{ gpio::{ gpioa::{PA1, PA7}, gpiob::{PB11, PB12, PB13}, gpioc::{PC4, PC5}, - gpiog::{PG11, PG13, PG14}, + gpiog::{PG11, PG13}, Input, Speed::VeryHigh, }, pac::{RCC, SYSCFG}, }; -#[cfg(feature = "stm32f7xx-hal")] +#[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] +use crate::hal::gpio::gpiog::PG14; + +#[cfg(any(feature = "stm32f7xx-hal", feature = "stm32h7xx-hal"))] use cortex_m::interrupt; -#[cfg(feature = "stm32f7xx-hal")] -use stm32f7xx_hal::{ - gpio::{ - gpioa::{PA1, PA7}, - gpiob::{PB11, PB12, PB13}, - gpioc::{PC4, PC5}, - gpiog::{PG11, PG13, PG14}, - Input, - Speed::VeryHigh, - }, - pac::{RCC, SYSCFG}, -}; +#[cfg(feature = "f-series")] +use crate::stm32::ETHERNET_MMC; -use crate::{ - dma::EthernetDMA, - stm32::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_MMC}, -}; +#[cfg(feature = "stm32h7xx-hal")] +use crate::stm32::ETHERNET_MTL; #[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; @@ -178,44 +206,21 @@ pin_trait!( [RmiiRxD1, "RMII RX Data Pin 1", "RXD1"] ); -/// Trait needed to setup the pins for the Ethernet peripheral. -pub trait AlternateVeryHighSpeed { - /// Puts the pin in the Alternate Function 11 with Very High Speed. - fn into_af11_very_high_speed(self); -} - /// A struct that contains all peripheral parts required to configure /// the ethernet peripheral. #[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. @@ -229,20 +234,10 @@ pub struct Parts<'rx, 'tx, T> { pub ptp: EthernetPTP, } -#[cfg(feature = "ptp")] -impl<'rx, 'tx, T> Parts<'rx, 'tx, T> { - /// Split this [`Parts`] into its components. - pub fn split(self) -> (T, EthernetDMA<'rx, 'tx>, EthernetPTP) { - (self.mac, self.dma, self.ptp) - } -} - -#[cfg(not(feature = "ptp"))] -impl<'rx, 'tx, T> Parts<'rx, 'tx, T> { - /// Split this [`Parts`] into its components. - pub fn split(self) -> (T, EthernetDMA<'rx, 'tx>) { - (self.mac, self.dma) - } +/// Trait needed to setup the pins for the Ethernet peripheral. +pub trait AlternateVeryHighSpeed { + /// Puts the pin in the Alternate Function 11 with Very High Speed. + fn into_af11_very_high_speed(self); } /// A struct that represents a combination of pins to be used @@ -305,6 +300,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: [