diff --git a/.vscode/settings.json b/.vscode/settings.json index 5888de33..b5feddb4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,4 +6,10 @@ ], "rust-analyzer.check.allTargets": false, "rust-analyzer.check.targets": "thumbv7em-none-eabihf", -} + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./Cargo.toml", + "./Cargo.toml", + "./Cargo.toml" + ], +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 32de7693..79f15a52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,17 @@ [package] name = "stm32h7xx-hal" version = "0.14.0" -authors = ["Andrew Straw ", - "Richard Meadows ", - "Henrik Böving ", - "Jan Adä ", - "Robert Jördens ", - "Ryan Summers ", - "Matthew Meyer ", - "Florian Jung ", - "Matt Ickstadt "] +authors = [ + "Andrew Straw ", + "Richard Meadows ", + "Henrik Böving ", + "Jan Adä ", + "Robert Jördens ", + "Ryan Summers ", + "Matthew Meyer ", + "Florian Jung ", + "Matt Ickstadt ", +] edition = "2021" rust-version = "1.65" categories = ["embedded", "hardware-support", "no-std"] @@ -22,7 +24,24 @@ readme = "README.md" exclude = [".gitignore"] [package.metadata.docs.rs] -features = ["stm32h743v", "rt", "xspi", "sdmmc", "sdmmc-fatfs", "fmc", "usb_hs", "rtc", "ethernet", "ltdc", "crc", "rand", "can", "defmt", "log", "fugit/defmt"] +features = [ + "stm32h743v", + "rt", + "xspi", + "sdmmc", + "sdmmc-fatfs", + "fmc", + "usb_hs", + "rtc", + "ethernet", + "ltdc", + "crc", + "rand", + "can", + "defmt", + "log", + "fugit/defmt", +] targets = ["thumbv7em-none-eabihf"] rustdoc-args = ["--cfg", "docsrs"] @@ -41,11 +60,15 @@ bare-metal = "1.0.0" sdio-host = { version = "0.9", optional = true } embedded-sdmmc = { version = "0.5", optional = true } stm32-fmc = { version = "0.3", optional = true } -synopsys-usb-otg = { version = "^0.3.0", features = ["cortex-m"], optional = true } +volatile-register = "0.2" +synopsys-usb-otg = { version = "^0.3.0", features = [ + "cortex-m", +], optional = true } embedded-display-controller = { version = "^0.1.0", optional = true } -log = { version = "0.4.14", optional = true} # see also the dev-dependencies section +log = { version = "0.4.14", optional = true } # see also the dev-dependencies section fdcan = { version = "0.2", optional = true } embedded-storage = "0.3" +futures = { version = "0.3", default-features = false, features = ["async-await"], optional = true } [dependencies.smoltcp] version = "0.10.0" @@ -72,7 +95,11 @@ panic-rtt-target = { version = "0.1.0", features = ["cortex-m"] } cfg-if = "1.0.0" rtt-target = "0.4.0" lazy_static = { version = "1.4.0", features = ["spin_no_std"] } -cortex-m-log = { version = "0.8.0", features = ["itm", "semihosting", "log-integration"] } +cortex-m-log = { version = "0.8.0", features = [ + "itm", + "semihosting", + "log-integration", +] } cortex-m-semihosting = "0.5.0" panic-itm = { version = "~0.4.1" } panic-semihosting = "0.6" @@ -88,18 +115,22 @@ default-features = false features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-raw"] [features] -default = ["rt"] +default = ["rt", + "ethernet", + "device-selected", "async-await"] device-selected = [] revision_v = [] -rm0433 = ["gpio-h747"] # aka. "single core" devices +rm0433 = ["gpio-h747"] # aka. "single core" devices rm0399 = ["gpio-h747"] # TODO: fix gpio # aka. "dual core" devices -rm0455 = ["gpio-h7a2"] # aka. "high memory integration" devices -rm0468 = ["gpio-h72"] # aka. "high speed" devices +rm0455 = ["gpio-h7a2"] # aka. "high memory integration" devices +rm0468 = ["gpio-h72"] # aka. "high speed" devices gpio-h72 = [] gpio-h747 = [] gpio-h7a2 = [] +async-await = ["dep:futures"] +ptp = ["smoltcp/packetmeta-id"] dsi = [] cm4 = [] cm7 = [] @@ -124,11 +155,43 @@ stm32h742v = ["stm32h7/stm32h743v", "device-selected", "revision_v", "rm0433"] stm32h743v = ["stm32h7/stm32h743v", "device-selected", "revision_v", "rm0433"] stm32h753v = ["stm32h7/stm32h753v", "device-selected", "revision_v", "rm0433"] stm32h750v = ["stm32h7/stm32h743v", "device-selected", "revision_v", "rm0433"] -stm32h747cm7 = ["stm32h7/stm32h747cm7", "device-selected", "revision_v", "rm0399", "cm7", "dsi", "smps"] -stm32h7b3 = ["stm32h7/stm32h7b3", "device-selected", "revision_v", "rm0455", "smps"] -stm32h7b0 = ["stm32h7/stm32h7b3", "device-selected", "revision_v", "rm0455", "smps"] -stm32h7a3 = ["stm32h7/stm32h7b3", "device-selected", "revision_v", "rm0455", "smps"] -stm32h735 = ["stm32h7/stm32h735", "device-selected", "revision_v", "rm0468", "smps"] # Also applies to 723,725,730,733 +stm32h747cm7 = [ + "stm32h7/stm32h747cm7", + "device-selected", + "revision_v", + "rm0399", + "cm7", + "dsi", + "smps", +] +stm32h7b3 = [ + "stm32h7/stm32h7b3", + "device-selected", + "revision_v", + "rm0455", + "smps", +] +stm32h7b0 = [ + "stm32h7/stm32h7b3", + "device-selected", + "revision_v", + "rm0455", + "smps", +] +stm32h7a3 = [ + "stm32h7/stm32h7b3", + "device-selected", + "revision_v", + "rm0455", + "smps", +] +stm32h735 = [ + "stm32h7/stm32h735", + "device-selected", + "revision_v", + "rm0468", + "smps", +] # Also applies to 723,725,730,733 # Flags for examples log-itm = [] log-rtt = [] @@ -137,15 +200,15 @@ example-smps = [] example-ldo = [] [profile.dev] -codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size in flash incremental = false [profile.release] codegen-units = 1 # better optimizations -debug = true # symbols are nice and they don't increase the size in flash -lto = true # better optimizations -opt-level = "s" # optimize for binary size +debug = true # symbols are nice and they don't increase the size in flash +lto = true # better optimizations +opt-level = "s" # optimize for binary size # The following examples do not build for all feature flag combinations. The # `required-features` field specifies the hal features and/or the hardware diff --git a/examples/ethernet-nucleo-h743zi2.rs b/examples/ethernet-nucleo-h743zi2.rs index 29fa24fc..e2db4852 100644 --- a/examples/ethernet-nucleo-h743zi2.rs +++ b/examples/ethernet-nucleo-h743zi2.rs @@ -23,7 +23,13 @@ mod utilities; use log::info; use stm32h7xx_hal::rcc::CoreClocks; -use stm32h7xx_hal::{ethernet, ethernet::PHY}; +use stm32h7xx_hal::{ + ethernet, + ethernet::{ + RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU, + PHY, + }, +}; use stm32h7xx_hal::{prelude::*, stm32, stm32::interrupt}; /// Configure SYSTICK for 1ms timebase @@ -49,9 +55,24 @@ static TIME: AtomicU32 = AtomicU32::new(0); /// Locally administered MAC address const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; -/// Ethernet descriptor rings are a global singleton +/// DesRing TD +const NUM_DESCRIPTORS: usize = 8; #[link_section = ".sram3.eth"] -static mut DES_RING: ethernet::DesRing<4, 4> = ethernet::DesRing::new(); +/// Doc +static mut TX_DESCRIPTORS: [TxDescriptor; NUM_DESCRIPTORS] = + [TxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_DESCRIPTORS: [RxDescriptor; NUM_DESCRIPTORS] = + [RxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; // the program entry point #[entry] @@ -104,6 +125,17 @@ fn main() -> ! { let rmii_txd0 = gpiog.pg13.into_alternate(); let rmii_txd1 = gpiob.pb13.into_alternate(); + let rmii_pins = ( + rmii_ref_clk, + rmii_mdio, + rmii_mdc, + rmii_crs_dv, + rmii_rxd0, + rmii_rxd1, + rmii_tx_en, + rmii_txd0, + rmii_txd1, + ); // Initialise ethernet... assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); // HCLK 200MHz assert_eq!(ccdr.clocks.pclk1().raw(), 100_000_000); // PCLK 100MHz @@ -111,36 +143,64 @@ fn main() -> ! { assert_eq!(ccdr.clocks.pclk4().raw(), 100_000_000); // PCLK 100MHz let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); - let (_eth_dma, eth_mac) = unsafe { - ethernet::new( - dp.ETHERNET_MAC, - dp.ETHERNET_MTL, - dp.ETHERNET_DMA, - ( - rmii_ref_clk, - rmii_mdio, - rmii_mdc, - rmii_crs_dv, - rmii_rxd0, - rmii_rxd1, - rmii_tx_en, - rmii_txd0, - rmii_txd1, - ), - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, + let (rx_ring, tx_ring) = { + // 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(unsafe { &mut RX_DESCRIPTORS }, unsafe { + &mut RX_BUFFERS + }), + TxDescriptorRing::new(unsafe { &mut TX_DESCRIPTORS }, unsafe { + &mut TX_BUFFERS + }), ) }; + #[cfg(feature = "ptp")] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + ptp: _ptp, + } = ethernet::new( + dp.ETHERNET_MAC, + dp.ETHERNET_MTL, + dp.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + + #[cfg(not(feature = "ptp"))] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + } = ethernet::new( + dp.ETHERNET_MAC, + dp.ETHERNET_MTL, + dp.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + // let start_addend = ptp.addend(); + eth_dma.enable_interrupt(); + // Initialise ethernet PHY... let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0)); lan8742a.phy_reset(); lan8742a.phy_init(); unsafe { - ethernet::enable_interrupt(); cp.NVIC.set_priority(stm32::Interrupt::ETH, 196); // Mid prio cortex_m::peripheral::NVIC::unmask(stm32::Interrupt::ETH); } @@ -180,7 +240,7 @@ fn main() -> ! { #[interrupt] fn ETH() { - unsafe { ethernet::interrupt_handler() } + ethernet::eth_interrupt_handler(); } #[exception] diff --git a/examples/ethernet-rtic-nucleo-h723zg.rs b/examples/ethernet-rtic-nucleo-h723zg.rs index fb13fb8d..89f9e07e 100644 --- a/examples/ethernet-rtic-nucleo-h723zg.rs +++ b/examples/ethernet-rtic-nucleo-h723zg.rs @@ -25,8 +25,13 @@ use smoltcp::iface::{Config, Interface, SocketSet, SocketStorage}; use smoltcp::time::Instant; use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr}; -use stm32h7xx_hal::{ethernet, rcc::CoreClocks, stm32}; - +use stm32h7xx_hal::{ + ethernet, + ethernet::{ + RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU, + }, +}; +use stm32h7xx_hal::{rcc::CoreClocks, stm32}; /// Configure SYSTICK for 1ms timebase fn systick_init(mut syst: stm32::SYST, clocks: CoreClocks) { let c_ck_mhz = clocks.c_ck().to_MHz(); @@ -45,9 +50,25 @@ static TIME: AtomicU32 = AtomicU32::new(0); /// Locally administered MAC address const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; +/// DesRing TD +const NUM_DESCRIPTORS: usize = 8; /// Ethernet descriptor rings are a global singleton -#[link_section = ".axisram.eth"] -static mut DES_RING: ethernet::DesRing<4, 4> = ethernet::DesRing::new(); +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_DESCRIPTORS: [TxDescriptor; NUM_DESCRIPTORS] = + [TxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_DESCRIPTORS: [RxDescriptor; NUM_DESCRIPTORS] = + [RxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; /// Net storage with static initialisation - another global singleton pub struct NetStorageStatic<'a> { @@ -60,13 +81,13 @@ static mut STORE: NetStorageStatic = NetStorageStatic { pub struct Net<'a> { iface: Interface, - ethdev: ethernet::EthernetDMA<4, 4>, + ethdev: ethernet::EthernetDMA<'a, 'a>, sockets: SocketSet<'a>, } impl<'a> Net<'a> { pub fn new( store: &'a mut NetStorageStatic<'a>, - mut ethdev: ethernet::EthernetDMA<4, 4>, + mut ethdev: ethernet::EthernetDMA<'a, 'a>, ethernet_addr: HardwareAddress, ) -> Self { let config = Config::new(ethernet_addr); @@ -153,6 +174,17 @@ mod app { let rmii_txd0 = gpiob.pb12.into_alternate(); let rmii_txd1 = gpiob.pb13.into_alternate(); + let rmii_pins = ( + rmii_ref_clk, + rmii_mdio, + rmii_mdc, + rmii_crs_dv, + rmii_rxd0, + rmii_rxd1, + rmii_tx_en, + rmii_txd0, + rmii_txd1, + ); // Initialise ethernet... assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); // HCLK 200MHz assert_eq!(ccdr.clocks.pclk1().raw(), 100_000_000); // PCLK 100MHz @@ -160,37 +192,64 @@ mod app { assert_eq!(ccdr.clocks.pclk4().raw(), 100_000_000); // PCLK 100MHz let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); - let (eth_dma, eth_mac) = unsafe { - ethernet::new( - ctx.device.ETHERNET_MAC, - ctx.device.ETHERNET_MTL, - ctx.device.ETHERNET_DMA, - ( - rmii_ref_clk, - rmii_mdio, - rmii_mdc, - rmii_crs_dv, - rmii_rxd0, - rmii_rxd1, - rmii_tx_en, - rmii_txd0, - rmii_txd1, - ), - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, + let (rx_ring, tx_ring) = { + // 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(unsafe { &mut RX_DESCRIPTORS }, unsafe { + &mut RX_BUFFERS + }), + TxDescriptorRing::new(unsafe { &mut TX_DESCRIPTORS }, unsafe { + &mut TX_BUFFERS + }), ) }; + #[cfg(feature = "ptp")] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + ptp: _ptp, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + + #[cfg(not(feature = "ptp"))] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + // let start_addend = ptp.addend(); + eth_dma.enable_interrupt(); + // Initialise ethernet PHY... let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac); lan8742a.phy_reset(); lan8742a.phy_init(); // The eth_dma should not be used until the PHY reports the link is up - unsafe { ethernet::enable_interrupt() }; - // unsafe: mutable reference to static storage, we only do this once let store = unsafe { &mut STORE }; let net = Net::new(store, eth_dma, mac_addr.into()); @@ -222,7 +281,7 @@ mod app { #[task(binds = ETH, local = [net])] fn ethernet_event(ctx: ethernet_event::Context) { - unsafe { ethernet::interrupt_handler() } + ethernet::eth_interrupt_handler(); let time = TIME.load(Ordering::Relaxed); ctx.local.net.poll(time as i64); diff --git a/examples/ethernet-rtic-stm32h735g-dk.rs b/examples/ethernet-rtic-stm32h735g-dk.rs index 033a8204..a4e78c4b 100644 --- a/examples/ethernet-rtic-stm32h735g-dk.rs +++ b/examples/ethernet-rtic-stm32h735g-dk.rs @@ -23,7 +23,13 @@ use smoltcp::iface::{Config, Interface, SocketSet, SocketStorage}; use smoltcp::time::Instant; use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr}; -use stm32h7xx_hal::{ethernet, rcc::CoreClocks, stm32}; +use stm32h7xx_hal::{ + ethernet, + ethernet::{ + RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU, + }, +}; +use stm32h7xx_hal::{rcc::CoreClocks, stm32}; /// Configure SYSTICK for 1ms timebase fn systick_init(mut syst: stm32::SYST, clocks: CoreClocks) { @@ -43,9 +49,25 @@ static TIME: AtomicU32 = AtomicU32::new(0); /// Locally administered MAC address const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; +/// DesRing TD +const NUM_DESCRIPTORS: usize = 8; /// Ethernet descriptor rings are a global singleton -#[link_section = ".axisram.eth"] -static mut DES_RING: ethernet::DesRing<4, 4> = ethernet::DesRing::new(); +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_DESCRIPTORS: [TxDescriptor; NUM_DESCRIPTORS] = + [TxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_DESCRIPTORS: [RxDescriptor; NUM_DESCRIPTORS] = + [RxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; // This data will be held by Net through a mutable reference pub struct NetStorageStatic<'a> { @@ -57,13 +79,13 @@ static mut STORE: MaybeUninit = MaybeUninit::uninit(); pub struct Net<'a> { iface: Interface, - ethdev: ethernet::EthernetDMA<4, 4>, + ethdev: ethernet::EthernetDMA<'a, 'a>, sockets: SocketSet<'a>, } impl<'a> Net<'a> { pub fn new( store: &'a mut NetStorageStatic<'a>, - mut ethdev: ethernet::EthernetDMA<4, 4>, + mut ethdev: ethernet::EthernetDMA<'a, 'a>, ethernet_addr: HardwareAddress, now: Instant, ) -> Self { @@ -149,6 +171,18 @@ mod app { let rmii_txd0 = gpiob.pb12.into_alternate(); let rmii_txd1 = gpiob.pb13.into_alternate(); + let rmii_pins = ( + rmii_ref_clk, + rmii_mdio, + rmii_mdc, + rmii_crs_dv, + rmii_rxd0, + rmii_rxd1, + rmii_tx_en, + rmii_txd0, + rmii_txd1, + ); + // Initialise ethernet... assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); // HCLK 200MHz assert_eq!(ccdr.clocks.pclk1().raw(), 100_000_000); // PCLK 100MHz @@ -156,37 +190,64 @@ mod app { assert_eq!(ccdr.clocks.pclk4().raw(), 100_000_000); // PCLK 100MHz let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); - let (eth_dma, eth_mac) = unsafe { - ethernet::new( - ctx.device.ETHERNET_MAC, - ctx.device.ETHERNET_MTL, - ctx.device.ETHERNET_DMA, - ( - rmii_ref_clk, - rmii_mdio, - rmii_mdc, - rmii_crs_dv, - rmii_rxd0, - rmii_rxd1, - rmii_tx_en, - rmii_txd0, - rmii_txd1, - ), - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, + let (rx_ring, tx_ring) = { + // 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(unsafe { &mut RX_DESCRIPTORS }, unsafe { + &mut RX_BUFFERS + }), + TxDescriptorRing::new(unsafe { &mut TX_DESCRIPTORS }, unsafe { + &mut TX_BUFFERS + }), ) }; + #[cfg(feature = "ptp")] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + ptp: _ptp, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + + #[cfg(not(feature = "ptp"))] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + // let start_addend = ptp.addend(); + eth_dma.enable_interrupt(); + // Initialise ethernet PHY... let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac); lan8742a.phy_reset(); lan8742a.phy_init(); // The eth_dma should not be used until the PHY reports the link is up - unsafe { ethernet::enable_interrupt() }; - // unsafe: mutable reference to static storage, we only do this once let store = unsafe { let store_ptr = STORE.as_mut_ptr(); @@ -231,7 +292,7 @@ mod app { #[task(binds = ETH, local = [net])] fn ethernet_event(ctx: ethernet_event::Context) { - unsafe { ethernet::interrupt_handler() } + ethernet::eth_interrupt_handler(); let time = TIME.load(Ordering::Relaxed); ctx.local.net.poll(time as i64); diff --git a/examples/ethernet-rtic-stm32h747i-disco.rs b/examples/ethernet-rtic-stm32h747i-disco.rs index 7d6525d7..59437594 100644 --- a/examples/ethernet-rtic-stm32h747i-disco.rs +++ b/examples/ethernet-rtic-stm32h747i-disco.rs @@ -30,7 +30,14 @@ use smoltcp::iface::{Config, Interface, SocketSet, SocketStorage}; use smoltcp::time::Instant; use smoltcp::wire::{HardwareAddress, IpAddress, IpCidr}; -use stm32h7xx_hal::{ethernet, rcc::CoreClocks, stm32}; +use stm32h7xx_hal::{rcc::CoreClocks, stm32}; + +use stm32h7xx_hal::{ + ethernet, + ethernet::{ + RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU, + }, +}; /// Configure SYSTICK for 1ms timebase fn systick_init(mut syst: stm32::SYST, clocks: CoreClocks) { @@ -50,9 +57,25 @@ static TIME: AtomicU32 = AtomicU32::new(0); /// Locally administered MAC address const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; +/// DesRing TD +const NUM_DESCRIPTORS: usize = 8; /// Ethernet descriptor rings are a global singleton #[link_section = ".sram3.eth"] -static mut DES_RING: ethernet::DesRing<4, 4> = ethernet::DesRing::new(); +/// Doc +static mut TX_DESCRIPTORS: [TxDescriptor; NUM_DESCRIPTORS] = + [TxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_DESCRIPTORS: [RxDescriptor; NUM_DESCRIPTORS] = + [RxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; // This data will be held by Net through a mutable reference pub struct NetStorageStatic<'a> { @@ -64,13 +87,13 @@ static mut STORE: MaybeUninit = MaybeUninit::uninit(); pub struct Net<'a> { iface: Interface, - ethdev: ethernet::EthernetDMA<4, 4>, + ethdev: ethernet::EthernetDMA<'a, 'a>, sockets: SocketSet<'a>, } impl<'a> Net<'a> { pub fn new( store: &'a mut NetStorageStatic<'a>, - mut ethdev: ethernet::EthernetDMA<4, 4>, + mut ethdev: ethernet::EthernetDMA<'a, 'a>, ethernet_addr: HardwareAddress, now: Instant, ) -> Self { @@ -159,6 +182,17 @@ mod app { let rmii_txd0 = gpiog.pg13.into_alternate(); let rmii_txd1 = gpiog.pg12.into_alternate(); // STM32H747I-DISCO + let rmii_pins = ( + rmii_ref_clk, + rmii_mdio, + rmii_mdc, + rmii_crs_dv, + rmii_rxd0, + rmii_rxd1, + rmii_tx_en, + rmii_txd0, + rmii_txd1, + ); // Initialise ethernet... assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); // HCLK 200MHz assert_eq!(ccdr.clocks.pclk1().raw(), 100_000_000); // PCLK 100MHz @@ -166,37 +200,64 @@ mod app { assert_eq!(ccdr.clocks.pclk4().raw(), 100_000_000); // PCLK 100MHz let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); - let (eth_dma, eth_mac) = unsafe { - ethernet::new( - ctx.device.ETHERNET_MAC, - ctx.device.ETHERNET_MTL, - ctx.device.ETHERNET_DMA, - ( - rmii_ref_clk, - rmii_mdio, - rmii_mdc, - rmii_crs_dv, - rmii_rxd0, - rmii_rxd1, - rmii_tx_en, - rmii_txd0, - rmii_txd1, - ), - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, + let (rx_ring, tx_ring) = { + // 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(unsafe { &mut RX_DESCRIPTORS }, unsafe { + &mut RX_BUFFERS + }), + TxDescriptorRing::new(unsafe { &mut TX_DESCRIPTORS }, unsafe { + &mut TX_BUFFERS + }), ) }; + #[cfg(feature = "ptp")] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + ptp: _ptp, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + + #[cfg(not(feature = "ptp"))] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + } = ethernet::new( + ctx.device.ETHERNET_MAC, + ctx.device.ETHERNET_MTL, + ctx.device.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + // let start_addend = ptp.addend(); + eth_dma.enable_interrupt(); + // Initialise ethernet PHY... let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac); lan8742a.phy_reset(); lan8742a.phy_init(); // The eth_dma should not be used until the PHY reports the link is up - unsafe { ethernet::enable_interrupt() }; - // unsafe: mutable reference to static storage, we only do this once let store = unsafe { let store_ptr = STORE.as_mut_ptr(); @@ -241,7 +302,7 @@ mod app { #[task(binds = ETH, local = [net])] fn ethernet_event(ctx: ethernet_event::Context) { - unsafe { ethernet::interrupt_handler() } + ethernet::eth_interrupt_handler(); let time = TIME.load(Ordering::Relaxed); ctx.local.net.poll(time as i64); diff --git a/examples/ethernet-stm32h747i-disco.rs b/examples/ethernet-stm32h747i-disco.rs index 206c4e43..d851136d 100644 --- a/examples/ethernet-stm32h747i-disco.rs +++ b/examples/ethernet-stm32h747i-disco.rs @@ -20,15 +20,37 @@ mod utilities; use log::info; -use stm32h7xx_hal::{ethernet, ethernet::PHY}; +use stm32h7xx_hal::{ + ethernet, + ethernet::{ + RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing, MTU, + PHY, + }, +}; use stm32h7xx_hal::{prelude::*, stm32, stm32::interrupt}; /// Locally administered MAC address const MAC_ADDRESS: [u8; 6] = [0x02, 0x00, 0x11, 0x22, 0x33, 0x44]; +/// DesRing TD +const NUM_DESCRIPTORS: usize = 8; /// Ethernet descriptor rings are a global singleton #[link_section = ".sram3.eth"] -static mut DES_RING: ethernet::DesRing<4, 4> = ethernet::DesRing::new(); +/// Doc +static mut TX_DESCRIPTORS: [TxDescriptor; NUM_DESCRIPTORS] = + [TxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut TX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_DESCRIPTORS: [RxDescriptor; NUM_DESCRIPTORS] = + [RxDescriptor::new(); NUM_DESCRIPTORS]; +#[link_section = ".sram3.eth"] +/// Doc +static mut RX_BUFFERS: [[u8; MTU + 2]; NUM_DESCRIPTORS] = + [[0u8; MTU + 2]; NUM_DESCRIPTORS]; // the program entry point #[entry] @@ -77,6 +99,17 @@ fn main() -> ! { let rmii_txd0 = gpiog.pg13.into_alternate(); let rmii_txd1 = gpiog.pg12.into_alternate(); + let rmii_pins = ( + rmii_ref_clk, + rmii_mdio, + rmii_mdc, + rmii_crs_dv, + rmii_rxd0, + rmii_rxd1, + rmii_tx_en, + rmii_txd0, + rmii_txd1, + ); // Initialise ethernet... assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); // HCLK 200MHz assert_eq!(ccdr.clocks.pclk1().raw(), 100_000_000); // PCLK 100MHz @@ -84,36 +117,64 @@ fn main() -> ! { assert_eq!(ccdr.clocks.pclk4().raw(), 100_000_000); // PCLK 100MHz let mac_addr = smoltcp::wire::EthernetAddress::from_bytes(&MAC_ADDRESS); - let (_eth_dma, eth_mac) = unsafe { - ethernet::new( - dp.ETHERNET_MAC, - dp.ETHERNET_MTL, - dp.ETHERNET_DMA, - ( - rmii_ref_clk, - rmii_mdio, - rmii_mdc, - rmii_crs_dv, - rmii_rxd0, - rmii_rxd1, - rmii_tx_en, - rmii_txd0, - rmii_txd1, - ), - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, + let (rx_ring, tx_ring) = { + // 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(unsafe { &mut RX_DESCRIPTORS }, unsafe { + &mut RX_BUFFERS + }), + TxDescriptorRing::new(unsafe { &mut TX_DESCRIPTORS }, unsafe { + &mut TX_BUFFERS + }), ) }; + #[cfg(feature = "ptp")] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + ptp: _ptp, + } = ethernet::new( + dp.ETHERNET_MAC, + dp.ETHERNET_MTL, + dp.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + + #[cfg(not(feature = "ptp"))] + let ethernet::Parts { + dma: eth_dma, + mac: eth_mac, + } = ethernet::new( + dp.ETHERNET_MAC, + dp.ETHERNET_MTL, + dp.ETHERNET_DMA, + rmii_pins, + rx_ring, + tx_ring, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ); + // let start_addend = ptp.addend(); + eth_dma.enable_interrupt(); + // Initialise ethernet PHY... let mut lan8742a = ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0)); lan8742a.phy_reset(); lan8742a.phy_init(); unsafe { - ethernet::enable_interrupt(); cp.NVIC.set_priority(stm32::Interrupt::ETH, 196); // Mid prio cortex_m::peripheral::NVIC::unmask(stm32::Interrupt::ETH); } @@ -143,7 +204,7 @@ fn main() -> ! { #[interrupt] fn ETH() { - unsafe { ethernet::interrupt_handler() } + ethernet::eth_interrupt_handler(); } #[exception] diff --git a/src/ethernet/cache.rs b/src/ethernet/cache.rs new file mode 100644 index 00000000..aa9ed036 --- /dev/null +++ b/src/ethernet/cache.rs @@ -0,0 +1,89 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +use super::PacketId; + +/// A cache for timestamping information, +/// used to lessen the size of Descriptors. +#[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/ethernet/eth.rs b/src/ethernet/eth.rs index d786dac0..5454cb60 100644 --- a/src/ethernet/eth.rs +++ b/src/ethernet/eth.rs @@ -19,29 +19,69 @@ //! > want to enable the cache, the simplest method would be to mark SRAM3 //! > as uncacheable via the MPU. //! +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project +//! //! [quartiq/stabilizer]: https://github.com/quartiq/stabilizer //! [notes]: https://github.com/quartiq/stabilizer/commit/ab1735950b2108eaa8d51eb63efadcd2e25c35c4 -use core::ptr; +use core::task::Poll; +#[cfg(feature = "ptp")] +use crate::ptp::{EthernetPTP, Timestamp}; use crate::rcc::{rec, CoreClocks, ResetEnable}; use crate::stm32; +use crate::stm32::{Interrupt, ETHERNET_DMA, NVIC}; +use futures::task::AtomicWaker; +#[cfg(feature = "ptp")] +use smoltcp::phy::PacketMeta; use smoltcp::{ self, - phy::{self, DeviceCapabilities}, + phy::{self, ChecksumCapabilities, DeviceCapabilities, RxToken, TxToken}, time::Instant, wire::EthernetAddress, }; +use super::rx::{RxDescriptor, RxDescriptorRing, RxError, RxPacket, RxRing}; +use super::tx::{TxDescriptor, TxDescriptorRing, TxError, TxPacket, TxRing}; + +use super::packet_id::PacketId; + +use crate::ethernet::raw_descriptor; use crate::{ - ethernet::{PinsRMII, StationManagement}, + ethernet::{self, PinsRMII, StationManagement}, gpio::Speed, }; +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. +#[allow(clippy::assertions_on_constants)] +const _ASSERT_DESCRIPTOR_SIZES: () = assert!(_RXDESC_SIZE == _TXDESC_SIZE); + +#[allow(clippy::assertions_on_constants)] +const _ASSERT_DESCRIPTOR_ALIGN: () = assert!(_RXDESC_SIZE % 4 == 0); + +const DESC_WORD_SKIP: u8 = + ((_RXDESC_SIZE / 4) - raw_descriptor::DESC_SIZE) as u8; + +#[allow(clippy::assertions_on_constants)] +const _ASSERT_DESC_WORD_SKIP_SIZE: () = assert!(DESC_WORD_SKIP <= 0b111); + // 6 DMAC, 6 SMAC, 4 q tag, 2 ethernet type II, 1500 ip MTU, 4 CRC, 2 // padding -const ETH_BUF_SIZE: usize = 1536; +// const ETH_BUF_SIZE: usize = 1536; + +#[cfg(feature = "ptp")] +pub const PTP_MAX_SIZE: usize = 76; +#[cfg(feature = "ptp")] +pub const MAX_PTP_FOLLOWERS: usize = 16; /// Transmit and Receive Descriptor fields #[allow(dead_code)] @@ -58,329 +98,53 @@ mod emac_consts { pub const EMAC_TDES2_B1L: u32 = 0x0000_3FFF; pub const EMAC_DES0_BUF1AP: u32 = 0xFFFF_FFFF; } -use self::emac_consts::*; - -/// Transmit Descriptor representation -/// -/// * tdes0: transmit buffer address -/// * tdes1: -/// * tdes2: buffer lengths -/// * tdes3: control and payload/frame length -/// -/// Note that Copy and Clone are derived to support initialising an -/// array of TDes, but you may not move a TDes after its address has -/// been given to the ETH_DMA engine. -#[derive(Copy, Clone)] -#[repr(C, packed)] -struct TDes { - tdes0: u32, - tdes1: u32, - tdes2: u32, - tdes3: u32, -} - -impl TDes { - /// Initialises this TDes to point at the given buffer. - pub fn init(&mut self) { - self.tdes0 = 0; - self.tdes1 = 0; - self.tdes2 = 0; - self.tdes3 = 0; // Owned by us - } - - /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { - self.tdes3 & EMAC_DES3_OWN == 0 - } -} - -/// Store a ring of TDes and associated buffers -#[repr(C, packed)] -struct TDesRing { - td: [TDes; TD], - tbuf: [[u32; ETH_BUF_SIZE / 4]; TD], - tdidx: usize, -} - -impl TDesRing { - const fn new() -> Self { - Self { - td: [TDes { - tdes0: 0, - tdes1: 0, - tdes2: 0, - tdes3: 0, - }; TD], - tbuf: [[0; ETH_BUF_SIZE / 4]; TD], - tdidx: 0, - } - } - - /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub fn init(&mut self) { - for x in 0..TD { - self.td[x].init(); - } - self.tdidx = 0; - - // Initialise pointers in the DMA engine. (There will be a memory barrier later - // before the DMA engine is enabled.) - unsafe { - let dma = &*stm32::ETHERNET_DMA::ptr(); - dma.dmactx_dlar - .write(|w| w.bits(&self.td[0] as *const _ as u32)); - dma.dmactx_rlr.write(|w| w.tdrl().bits(TD as u16 - 1)); - dma.dmactx_dtpr - .write(|w| w.bits(&self.td[0] as *const _ as u32)); - } - } - - /// Return true if a TDes is available for use - pub fn available(&self) -> bool { - self.td[self.tdidx].available() - } - - /// Release the next TDes to the DMA engine for transmission - pub fn release(&mut self) { - let x = self.tdidx; - assert!(self.td[x].tdes3 & EMAC_DES3_OWN == 0); // Owned by us - - let address = ptr::addr_of!(self.tbuf[x]) as u32; - - // Read format - self.td[x].tdes0 = address; // Buffer 1 - self.td[x].tdes1 = 0; // Not used - assert!(self.td[x].tdes2 & !EMAC_TDES2_B1L == 0); // Not used - assert!(self.td[x].tdes2 & EMAC_TDES2_B1L > 0); // Length must be valid - self.td[x].tdes3 = 0; - self.td[x].tdes3 |= EMAC_DES3_FD; // FD: Contains first buffer of packet - self.td[x].tdes3 |= EMAC_DES3_LD; // LD: Contains last buffer of packet - self.td[x].tdes3 |= EMAC_DES3_OWN; // Give the DMA engine ownership - - // Ensure changes to the descriptor are committed before - // DMA engine sees tail pointer store - cortex_m::asm::dsb(); - - // Move the tail pointer (TPR) to the next descriptor - let x = (x + 1) % TD; - unsafe { - let dma = &*stm32::ETHERNET_DMA::ptr(); - dma.dmactx_dtpr - .write(|w| w.bits(&(self.td[x]) as *const _ as u32)); - } - - self.tdidx = x; - } - - /// Access the buffer pointed to by the next TDes - pub unsafe fn buf_as_slice_mut(&mut self, length: usize) -> &mut [u8] { - let x = self.tdidx; - - // Set address in descriptor - self.td[x].tdes0 = ptr::addr_of!(self.tbuf[x]) as u32; // Buffer 1 - - // Set length in descriptor - let len = core::cmp::min(length, ETH_BUF_SIZE); - self.td[x].tdes2 = (length as u32) & EMAC_TDES2_B1L; - - // Create a raw pointer in place without an intermediate reference. Use - // this to return a slice from the packed buffer - let addr = ptr::addr_of_mut!(self.tbuf[x]) as *mut _; - core::slice::from_raw_parts_mut(addr, len) - } -} - -/// Receive Descriptor representation -/// -/// * rdes0: recieve buffer address -/// * rdes1: -/// * rdes2: -/// * rdes3: OWN and Status -/// -/// Note that Copy and Clone are derived to support initialising an -/// array of RDes, but you may not move a RDes after its address has -/// been given to the ETH_DMA engine. -#[derive(Copy, Clone)] -#[repr(C, packed)] -struct RDes { - rdes0: u32, - rdes1: u32, - rdes2: u32, - rdes3: u32, -} - -impl RDes { - /// Initialises RDes - pub fn init(&mut self) { - self.rdes0 = 0; - self.rdes1 = 0; - self.rdes2 = 0; - self.rdes3 = 0; // Owned by us - } - /// Return true if this RDes is acceptable to us - pub fn valid(&self) -> bool { - // Write-back descriptor is valid if: - // - // Contains first buffer of packet AND contains last buf of - // packet AND no errors AND not a contex descriptor - self.rdes3 - & (EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_ES | EMAC_DES3_CTXT) - == (EMAC_DES3_FD | EMAC_DES3_LD) - } - - /// Return true if this RDes is not currently owned by the DMA - pub fn available(&self) -> bool { - self.rdes3 & EMAC_DES3_OWN == 0 // Owned by us - } +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq)] +/// This struct is returned if a packet ID is not associated +/// with any TX or RX descriptors. +pub struct PacketIdNotFound; + +#[cfg(feature = "ptp")] +#[derive(Clone, Copy)] +pub struct PtpFrameWithId { + pub ptp_frame: [u8; PTP_MAX_SIZE], + pub packet_id: Option, } -/// Store a ring of RDes and associated buffers -#[repr(C, packed)] -struct RDesRing { - rd: [RDes; RD], - rbuf: [[u32; ETH_BUF_SIZE / 4]; RD], - rdidx: usize, +/// Ethernet DMA. +pub struct EthernetDMA<'rx, 'tx> { + eth_dma: ETHERNET_DMA, + // eth_mtl: crate::stm32::ETHERNET_MTL, + rx_ring: RxRing<'rx>, + tx_ring: TxRing<'tx>, + + #[cfg(feature = "ptp")] + packet_id_counter: u32, + #[cfg(feature = "ptp")] + ptp_frame_buffer: [PtpFrameWithId; MAX_PTP_FOLLOWERS], + #[cfg(feature = "ptp")] + write_pos: usize, } -impl RDesRing { - const fn new() -> Self { - Self { - rd: [RDes { - rdes0: 0, - rdes1: 0, - rdes2: 0, - rdes3: 0, - }; RD], - rbuf: [[0; ETH_BUF_SIZE / 4]; RD], - rdidx: 0, - } - } - - /// Initialise this RDesRing. Assume RDesRing is corrupt - /// - /// The current memory address of the buffers inside this RDesRing - /// will be stored in the descriptors, so ensure the RDesRing is - /// not moved after initialisation. - pub fn init(&mut self) { - for x in 0..RD { - self.rd[x].init(); - } - self.rdidx = 0; - - // Initialise pointers in the DMA engine - unsafe { - let dma = &*stm32::ETHERNET_DMA::ptr(); - dma.dmacrx_dlar - .write(|w| w.bits(&self.rd[0] as *const _ as u32)); - dma.dmacrx_rlr.write(|w| w.rdrl().bits(RD as u16 - 1)); - } - - // Release descriptors to the DMA engine - while self.available() { - self.release() - } - } - - /// Return true if a RDes is available for use - pub fn available(&self) -> bool { - self.rd[self.rdidx].available() - } - - /// Return true if current RDes is valid - pub fn valid(&self) -> bool { - self.rd[self.rdidx].valid() - } - - /// Release the next RDes to the DMA engine - pub fn release(&mut self) { - let x = self.rdidx; - assert!(self.rd[x].rdes3 & EMAC_DES3_OWN == 0); // Owned by us - - let address = ptr::addr_of!(self.rbuf[x]) as u32; - - // Read format - self.rd[x].rdes0 = address; // Buffer 1 - self.rd[x].rdes1 = 0; // Reserved - self.rd[x].rdes2 = 0; // Marked as invalid - self.rd[x].rdes3 = 0; - self.rd[x].rdes3 |= EMAC_DES3_OWN; // Give the DMA engine ownership - self.rd[x].rdes3 |= EMAC_RDES3_BUF1V; // BUF1V: 1st buffer address is valid - self.rd[x].rdes3 |= EMAC_RDES3_IOC; // IOC: Interrupt on complete - - // Ensure changes to the descriptor are committed before - // DMA engine sees tail pointer store - cortex_m::asm::dsb(); - - // Move the tail pointer (TPR) to this descriptor - unsafe { - let dma = &*stm32::ETHERNET_DMA::ptr(); - dma.dmacrx_dtpr - .write(|w| w.bits(&(self.rd[x]) as *const _ as u32)); - } - - // Update active descriptor - self.rdidx = (x + 1) % RD; - } - - /// Access the buffer pointed to by the next RDes - /// - /// # Safety - /// - /// Ensure that release() is called between subsequent calls to this - /// function. - #[allow(clippy::mut_from_ref)] - pub unsafe fn buf_as_slice_mut(&self) -> &mut [u8] { - let x = self.rdidx; - - // Write-back format - let addr = ptr::addr_of!(self.rbuf[x]) as *mut u8; - let len = (self.rd[x].rdes3 & EMAC_RDES3_PL) as usize; - - let len = core::cmp::min(len, ETH_BUF_SIZE); - core::slice::from_raw_parts_mut(addr, len) - } -} - -pub struct DesRing { - tx: TDesRing, - rx: RDesRing, -} -impl DesRing { - pub const fn new() -> Self { - DesRing { - tx: TDesRing::new(), - rx: RDesRing::new(), - } - } -} -impl Default for DesRing { - fn default() -> Self { - Self::new() - } -} - -/// -/// Ethernet DMA -/// -pub struct EthernetDMA { - ring: &'static mut DesRing, - eth_dma: stm32::ETHERNET_DMA, -} - -/// /// Ethernet MAC -/// pub struct EthernetMAC { eth_mac: stm32::ETHERNET_MAC, eth_phy_addr: u8, clock_range: u8, } +/// Access to all configured parts of the ethernet peripheral. +pub struct Parts<'rx, 'tx> { + /// Access to and control over the ethernet MAC. + pub mac: EthernetMAC, + /// Access to and control over the ethernet DMA. + pub dma: EthernetDMA<'rx, 'tx>, + /// Access to and control over the ethernet PTP module. + #[cfg(feature = "ptp")] + pub ptp: EthernetPTP, +} + /// Create and initialise the ethernet driver. /// /// You must move in ETH_MAC, ETH_MTL, ETH_DMA. @@ -400,19 +164,23 @@ pub struct EthernetMAC { /// /// `EthernetDMA` shall not be moved as it is initialised here #[allow(clippy::too_many_arguments)] -pub fn new( +pub fn new<'rx, 'tx>( eth_mac: stm32::ETHERNET_MAC, eth_mtl: stm32::ETHERNET_MTL, eth_dma: stm32::ETHERNET_DMA, mut pins: impl PinsRMII, - ring: &'static mut DesRing, + rx_buffer: RxDescriptorRing<'rx>, + tx_buffer: TxDescriptorRing<'tx>, mac_addr: EthernetAddress, prec: rec::Eth1Mac, clocks: &CoreClocks, -) -> (EthernetDMA, EthernetMAC) { +) -> Parts<'rx, 'tx> { pins.set_speed(Speed::VeryHigh); unsafe { - new_unchecked(eth_mac, eth_mtl, eth_dma, ring, mac_addr, prec, clocks) + new_unchecked( + eth_mac, eth_mtl, eth_dma, rx_buffer, tx_buffer, mac_addr, prec, + clocks, + ) } } @@ -438,15 +206,17 @@ pub fn new( /// # Safety /// /// `EthernetDMA` shall not be moved as it is initialised here -pub unsafe fn new_unchecked( +#[allow(clippy::too_many_arguments)] +pub unsafe fn new_unchecked<'rx, 'tx>( eth_mac: stm32::ETHERNET_MAC, eth_mtl: stm32::ETHERNET_MTL, eth_dma: stm32::ETHERNET_DMA, - ring: &'static mut DesRing, + rx_buffer: RxDescriptorRing<'rx>, + tx_buffer: TxDescriptorRing<'tx>, mac_addr: EthernetAddress, prec: rec::Eth1Mac, clocks: &CoreClocks, -) -> (EthernetDMA, EthernetMAC) { +) -> Parts<'rx, 'tx> { // RCC { let rcc = &*stm32::RCC::ptr(); @@ -467,10 +237,6 @@ pub unsafe fn new_unchecked( syscfg.pmcr.modify(|_, w| w.epis().bits(0b100)); // RMII } - // reset ETH_MAC - write 1 then 0 - //rcc.ahb1rstr.modify(|_, w| w.eth1macrst().set_bit()); - //rcc.ahb1rstr.modify(|_, w| w.eth1macrst().clear_bit()); - cortex_m::interrupt::free(|_cs| { // reset ETH_DMA - write 1 and wait for 0 eth_dma.dmamr.modify(|_, w| w.swr().set_bit()); @@ -513,6 +279,7 @@ pub unsafe fn new_unchecked( .dr() .set_bit() }); + eth_mac.macecr.modify(|_, w| { w.eipgen() .clear_bit() @@ -541,36 +308,72 @@ pub unsafe fn new_unchecked( }); // frame filter register eth_mac.macpfr.modify(|_, w| { - w.dntu() - .clear_bit() - .ipfe() - .clear_bit() - .vtfe() - .clear_bit() - .hpf() - .clear_bit() - .saf() - .clear_bit() - .saif() - .clear_bit() - .pcf() - .bits(0b00) - .dbf() - .clear_bit() - .pm() - .clear_bit() - .daif() - .clear_bit() - .hmc() - .clear_bit() - .huc() - .clear_bit() - // Receive All - .ra() - .clear_bit() - // Promiscuous mode - .pr() - .clear_bit() + #[cfg(feature = "ptp")] + { + w.dntu() + .clear_bit() + .ipfe() + .clear_bit() + .vtfe() + .clear_bit() + .hpf() + .clear_bit() + .saf() + .clear_bit() + .saif() + .clear_bit() + .pcf() + .bits(0b00) + .dbf() + .clear_bit() + .pm() + .set_bit() + .daif() + .clear_bit() + .hmc() + .clear_bit() + .huc() + .clear_bit() + // Receive All + .ra() + .clear_bit() + // Promiscuous mode + .pr() + .clear_bit() + } + #[cfg(not(feature = "ptp"))] + { + w.dntu() + .clear_bit() + .ipfe() + .clear_bit() + .vtfe() + .clear_bit() + .hpf() + .clear_bit() + .saf() + .clear_bit() + .saif() + .clear_bit() + .pcf() + .bits(0b00) + .dbf() + .clear_bit() + .pm() + .clear_bit() + .daif() + .clear_bit() + .hmc() + .clear_bit() + .huc() + .clear_bit() + // Receive All + .ra() + .clear_bit() + // Promiscuous mode + .pr() + .clear_bit() + } }); eth_mac.macwtr.write(|w| w.pwe().clear_bit()); // Flow Control Register @@ -641,35 +444,24 @@ pub unsafe fn new_unchecked( // operation mode register eth_dma.dmamr.modify(|_, w| { - w.intm() - .bits(0b00) - // Rx Tx priority ratio 1:1 + w + // Rx Tx priority ratio 2:1 .pr() - .bits(0b000) - .txpr() - .clear_bit() - .da() - .clear_bit() + .variant(0b001) }); // bus mode register eth_dma.dmasbmr.modify(|_, w| { // Address-aligned beats - w.aal() - .set_bit() - // Fixed burst - .fb() - .set_bit() + w.aal().set_bit() }); eth_dma .dmaccr - .modify(|_, w| w.dsl().bits(0).pblx8().clear_bit().mss().bits(536)); + .modify(|_, w| w.dsl().variant(DESC_WORD_SKIP)); eth_dma.dmactx_cr.modify(|_, w| { w // Tx DMA PBL .txpbl() .bits(32) - .tse() - .clear_bit() // Operate on second frame .osf() .clear_bit() @@ -679,19 +471,12 @@ pub unsafe fn new_unchecked( w // receive buffer size .rbsz() - .bits(ETH_BUF_SIZE as u16) + .variant(rx_buffer.first_buffer().len() as u16) // Rx DMA PBL .rxpbl() .bits(32) - // Disable flushing of received frames - .rpf() - .clear_bit() }); - // Initialise DMA descriptors - ring.tx.init(); - ring.rx.init(); - // Ensure the DMA descriptors are committed cortex_m::asm::dsb(); @@ -704,13 +489,9 @@ pub unsafe fn new_unchecked( }); eth_mtl.mtltx_qomr.modify(|_, w| w.ftq().set_bit()); - // Manage DMA transmission and reception - eth_dma.dmactx_cr.modify(|_, w| w.st().set_bit()); - eth_dma.dmacrx_cr.modify(|_, w| w.sr().set_bit()); - eth_dma .dmacsr - .modify(|_, w| w.tps().set_bit().rps().set_bit()); + .modify(|_, w| w.tps().set_bit().rps().set_bit()); //These bits are set if transmission is stopped. }); // MAC layer @@ -736,9 +517,72 @@ pub unsafe fn new_unchecked( clock_range: csr_clock_range, }; - let dma = EthernetDMA { ring, eth_dma }; + let mut dma = EthernetDMA { + eth_dma, + // eth_mtl, + rx_ring: RxRing::new(rx_buffer), + tx_ring: TxRing::new(tx_buffer), + + #[cfg(feature = "ptp")] + packet_id_counter: 0, + #[cfg(feature = "ptp")] + ptp_frame_buffer: [PtpFrameWithId { + ptp_frame: [0u8; PTP_MAX_SIZE], + packet_id: None, + }; MAX_PTP_FOLLOWERS], + #[cfg(feature = "ptp")] + write_pos: 0, + }; + dma.rx_ring.start(&dma.eth_dma); + dma.tx_ring.start(&dma.eth_dma); + + // Configure the ethernet PTP + #[cfg(feature = "ptp")] + let ptp = EthernetPTP::new(*clocks, &dma); + + Parts { + mac, + dma, + #[cfg(feature = "ptp")] + ptp, + } +} - (dma, mac) +/// A summary of the reasons for the occurence of an +/// interrupt +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct InterruptReason { + /// A packet has arrived and is ready for processing. + pub rx: bool, + /// A packet was sent, and a TX slot has freed up. + pub tx: bool, + /// A DMA error occured. + pub dma_error: bool, + #[cfg(all(feature = "ptp"))] + /// The target time configured for PTP has + /// passed. + pub time_passed: bool, +} + +/// Handle the `ETH` interrupt. +/// +/// This function wakes wakers and resets +/// interrupt bits relevant in that interrupt. +#[cfg(feature = "device-selected")] +pub fn eth_interrupt_handler() -> InterruptReason { + let dma = EthernetDMA::interrupt_handler(); + + #[cfg(feature = "ptp")] + let is_time_trigger = EthernetPTP::interrupt_handler(); + + InterruptReason { + rx: dma.is_rx, + tx: dma.is_tx, + dma_error: dma.is_error, + #[cfg(all(feature = "ptp"))] + time_passed: is_time_trigger, + } } impl EthernetMAC { @@ -795,111 +639,571 @@ impl StationManagement for EthernetMAC { } } -/// Define TxToken type and implement consume method -pub struct TxToken<'a, const TD: usize>(&'a mut TDesRing); +/// An Ethernet RX token that can be consumed in order to receive +/// an ethernet packet. +pub struct EthRxToken<'a, 'rx> { + rx_ring: &'a mut RxRing<'rx>, + #[cfg(feature = "ptp")] + meta: PacketId, + #[cfg(feature = "ptp")] + buf: &'a mut PtpFrameWithId, +} -impl<'a, const TD: usize> phy::TxToken for TxToken<'a, TD> { - fn consume(self, len: usize, f: F) -> R +impl<'dma, 'rx> RxToken for EthRxToken<'dma, 'rx> { + fn consume(self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - assert!(len <= ETH_BUF_SIZE); - - let result = f(unsafe { self.0.buf_as_slice_mut(len) }); - self.0.release(); + #[cfg(feature = "ptp")] + let meta = Some(self.meta); + + #[cfg(not(feature = "ptp"))] + let meta = None; + + // NOTE(unwrap): an `EthRxToken` is only created when `eth.rx_available()` + let mut packet = self.rx_ring.recv_next(meta).ok().unwrap(); + #[cfg(feature = "ptp")] + { + let ethertype = + u16::from_be_bytes(packet[12..14].try_into().unwrap()); + if ethertype == 0x88F7 { + let packet_buf = &packet[14..]; + ((self.buf.ptp_frame)[0..packet_buf.len()]) + .copy_from_slice(packet_buf); + self.buf.packet_id = meta; + } + } + let result = f(&mut packet); + packet.free(); result } + + #[cfg(feature = "ptp")] + fn meta(&self) -> smoltcp::phy::PacketMeta { + self.meta.into() + } } -/// Define RxToken type and implement consume method -pub struct RxToken<'a, const RD: usize>(&'a mut RDesRing); +/// Just a reference to [`EthernetDMA`] for sending a +/// packet later with [`TxToken::consume()`]. +pub struct EthTxToken<'a, 'tx> { + tx_ring: &'a mut TxRing<'tx>, + #[cfg(feature = "ptp")] + meta: Option, +} -impl<'a, const RD: usize> phy::RxToken for RxToken<'a, RD> { - fn consume(self, f: F) -> R +impl<'dma, 'tx> TxToken for EthTxToken<'dma, 'tx> { + fn consume(self, len: usize, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - let result = f(unsafe { self.0.buf_as_slice_mut() }); - self.0.release(); - result + #[cfg(feature = "ptp")] + let meta = self.meta.map(Into::into); + #[cfg(not(feature = "ptp"))] + let meta = None; + + // NOTE(unwrap): an `EthTxToken` is only created if + // there is a descriptor available for sending. + let mut tx_packet = self.tx_ring.send_next(len, meta).ok().unwrap(); + let res = f(&mut tx_packet); + tx_packet.send(); + res + } + + #[cfg(feature = "ptp")] + fn set_meta(&mut self, meta: PacketMeta) { + self.meta = Some(meta.into()); } } -/// Implement the smoltcp Device interface -impl phy::Device for EthernetDMA { - type RxToken<'a> = RxToken<'a, RD>; - type TxToken<'a> = TxToken<'a, TD>; +impl<'a, 'rx, 'tx> phy::Device for &'a mut EthernetDMA<'rx, 'tx> { + type RxToken<'token> = EthRxToken<'token, 'rx> where Self: 'token; + type TxToken<'token> = EthTxToken<'token, 'tx> where Self: 'token; - // Clippy false positive because DeviceCapabilities is non-exhaustive - #[allow(clippy::field_reassign_with_default)] fn capabilities(&self) -> DeviceCapabilities { let mut caps = DeviceCapabilities::default(); - // ethernet frame type II (6 smac, 6 dmac, 2 ethertype), - // sans CRC (4), 1500 IP MTU - caps.max_transmission_unit = 1514; - caps.max_burst_size = Some(core::cmp::min(TD, RD)); + caps.max_transmission_unit = ethernet::MTU; + caps.max_burst_size = Some(1); + caps.checksum = ChecksumCapabilities::ignored(); caps } fn receive( &mut self, _timestamp: Instant, - ) -> Option<(RxToken, TxToken)> { - // Skip all queued packets with errors. - while self.ring.rx.available() && !self.ring.rx.valid() { - self.ring.rx.release() + ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.tx_available() && self.rx_available() { + #[cfg(feature = "ptp")] + let rx_packet_id = self.next_packet_id(); + + let EthernetDMA { + rx_ring, tx_ring, .. + } = self; + + let rx = EthRxToken { + rx_ring, + #[cfg(feature = "ptp")] + meta: rx_packet_id, + #[cfg(feature = "ptp")] + buf: &mut self.ptp_frame_buffer[self.write_pos], + }; + + let tx = EthTxToken { + tx_ring, + #[cfg(feature = "ptp")] + meta: None, + }; + Some((rx, tx)) + } else { + None } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + if self.tx_available() { + #[cfg(feature = "ptp")] + let tx_packet_id = self.next_packet_id(); + + let EthernetDMA { tx_ring, .. } = self; + Some(EthTxToken { + tx_ring, + #[cfg(feature = "ptp")] + meta: Some(tx_packet_id), + }) + } else { + None + } + } +} + +/// Use this Ethernet driver with [smoltcp](https://github.com/smoltcp-rs/smoltcp) +impl<'rx, 'tx> phy::Device for EthernetDMA<'rx, 'tx> { + type RxToken<'token> = EthRxToken<'token, 'rx> where Self: 'token; + type TxToken<'token> = EthTxToken<'token, 'tx> where Self: 'token; + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = ethernet::MTU; + caps.max_burst_size = Some(1); + caps.checksum = ChecksumCapabilities::ignored(); + caps + } - if self.ring.rx.available() && self.ring.tx.available() { - Some((RxToken(&mut self.ring.rx), TxToken(&mut self.ring.tx))) + fn receive( + &mut self, + _timestamp: Instant, + ) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.tx_available() && self.rx_available() { + #[cfg(feature = "ptp")] + let rx_packet_id = self.next_packet_id(); + + let EthernetDMA { + rx_ring, tx_ring, .. + } = self; + + let rx = EthRxToken { + rx_ring, + #[cfg(feature = "ptp")] + meta: rx_packet_id, + #[cfg(feature = "ptp")] + buf: &mut self.ptp_frame_buffer[self.write_pos], + }; + + #[cfg(feature = "ptp")] + { + self.write_pos = (self.write_pos + 1) % MAX_PTP_FOLLOWERS; + } + + let tx = EthTxToken { + tx_ring, + #[cfg(feature = "ptp")] + meta: None, + }; + Some((rx, tx)) } else { None } } - fn transmit(&mut self, _timestamp: Instant) -> Option> { - if self.ring.tx.available() { - Some(TxToken(&mut self.ring.tx)) + fn transmit(&mut self, _timestamp: Instant) -> Option> { + if self.tx_available() { + #[cfg(feature = "ptp")] + let tx_packet_id = self.next_packet_id(); + + let EthernetDMA { tx_ring, .. } = self; + Some(EthTxToken { + tx_ring, + #[cfg(feature = "ptp")] + meta: Some(tx_packet_id), + }) } else { None } } } -impl EthernetDMA { +#[allow(clippy::extra_unused_lifetimes)] +impl<'a, 'rx, 'tx> EthernetDMA<'rx, 'tx> { + #[cfg(feature = "ptp")] + pub fn get_frame_from( + &'a self, + clock_identity: u64, + ) -> Option<(&'a PtpFrameWithId, usize)> { + for i in 0..MAX_PTP_FOLLOWERS { + if self.ptp_frame_buffer[i].packet_id.is_some() { + // defmt::info!("buffer = {}", self.ptp_frame_buffer[i].0); + if u64::from_be_bytes( + self.ptp_frame_buffer[i].ptp_frame[20..28] + .try_into() + .unwrap(), + ) == clock_identity + { + return Some((&self.ptp_frame_buffer[i], i)); + } + } + } + None + } + #[cfg(feature = "ptp")] + pub fn invalidate_frame_at(&'a mut self, pos: usize) { + if pos < MAX_PTP_FOLLOWERS { + self.ptp_frame_buffer[pos].packet_id = None; + } + } + #[cfg(feature = "ptp")] + pub fn send_ptp_frame( + frame: &[u8], + tx_option: Option< + as phy::Device>::TxToken<'_>, + >, + meta: PacketId, + ) { + if let Some(mut tx_token) = tx_option { + tx_token.set_meta(meta.into()); + tx_token.consume(frame.len(), |buf| { + buf[..frame.len()].copy_from_slice(frame); + }); + } + } /// Return the number of packets dropped since this method was /// last called pub fn number_packets_dropped(&self) -> u32 { self.eth_dma.dmacmfcr.read().mfc().bits() as u32 } + + fn eth_dma(&self) -> ÐERNET_DMA { + &self.eth_dma + } + + /// Split the [`EthernetDMA`] into concurrently operating send and + /// receive parts. + pub fn split(&mut self) -> (&mut RxRing<'rx>, &mut TxRing<'tx>) { + (&mut self.rx_ring, &mut self.tx_ring) + } + + /// Enable RX and TX interrupts + /// + /// In your handler you must call + /// [`EthernetDMA::interrupt_handler()`] or [`stm32_eth::eth_interrupt_handler`](crate::eth_interrupt_handler) + /// to clear interrupt pending bits. Otherwise the interrupt will reoccur immediately. + /// + /// [`EthernetPTP::interrupt_handler()`]: crate::ptp::EthernetPTP::interrupt_handler + #[cfg_attr( + feature = "ptp", + 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().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); + } + } + + pub 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(crate) fn interrupt_handler() -> InterruptReasonSummary { + // SAFETY: we only perform atomic reads/writes through `eth_dma`. + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + 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(), + ) + }; + + let status = InterruptReasonSummary { + is_rx, + is_tx, + is_error, + }; + + #[cfg(feature = "async-await")] + { + if status.is_tx { + EthernetDMA::tx_waker().wake(); + } + + if status.is_rx { + EthernetDMA::rx_waker().wake(); + } + } + + status + } + + /// Try to receive a packet. + /// + /// If no packet is available, this function returns [`Err(RxError::WouldBlock)`](RxError::WouldBlock). + /// + /// It may also return another kind of [`RxError`]. + pub fn recv_next( + &mut self, + packet_id: Option, + ) -> Result { + self.rx_ring.recv_next(packet_id.map(Into::into)) + } + + /// Is Rx DMA currently running? + /// + /// 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 { + RxRing::running_state().is_running() + } + + /// Is Tx DMA currently running? + pub fn tx_is_running(&self) -> bool { + TxRing::is_running() + } + + /// Try to send a packet with data. + /// + /// If there are no free TX slots, this function will + /// return [`Err(TxError::WouldBlock)`](TxError::WouldBlock). + pub fn send( + &mut self, + length: usize, + packet_id: Option, + f: F, + ) -> Result<(), TxError> + where + F: FnOnce(&mut [u8]), + { + let mut tx_packet = self.tx_ring.send_next(length, packet_id)?; + f(&mut tx_packet); + tx_packet.send(); + + Ok(()) + } + + /// Check if there is a packet available for reading. + /// + /// If this function returns true, it is guaranteed that the + /// next call to [`EthernetDMA::recv_next`] will return [`Ok`]. + pub fn rx_available(&mut self) -> bool { + self.rx_ring.next_entry_available() + } + + /// Check if sending a packet now would succeed. + /// + /// If this function returns true, it is guaranteed that + /// the next call to [`EthernetDMA::send`] will return [`Ok`] + pub fn tx_available(&mut self) -> bool { + self.tx_ring.next_entry_available() + } } -/// Clears the Ethernet interrupt flag -/// -/// # Safety -/// -/// This method implements a single register write to DMACSR -pub unsafe fn interrupt_handler() { - let eth_dma = &*stm32::ETHERNET_DMA::ptr(); - eth_dma - .dmacsr - .write(|w| w.nis().set_bit().ri().set_bit().ti().set_bit()); - let _ = eth_dma.dmacsr.read(); - let _ = eth_dma.dmacsr.read(); // Delay 2 peripheral clocks +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()); + } } -/// Enables the Ethernet Interrupt. The following interrupts are enabled: -/// -/// * Normal Interrupt `NIE` -/// * Receive Interrupt `RIE` -/// * Transmit Interript `TIE` -/// -/// # Safety -/// -/// This method implements a single RMW to DMACIER -pub unsafe fn enable_interrupt() { - let eth_dma = &*stm32::ETHERNET_DMA::ptr(); - eth_dma - .dmacier - .modify(|_, w| w.nie().set_bit().rie().set_bit().tie().set_bit()); +#[cfg(feature = "async-await")] +impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { + pub(crate) fn rx_waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + + pub(crate) fn tx_waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + + /// Receive a packet. + /// + /// See [`RxRing::recv`]. + pub async fn recv(&mut self, packet_id: Option) -> RxPacket { + self.rx_ring.recv(packet_id).await + } + + /// Prepare a packet for sending. + /// + /// See [`TxRing::prepare_packet`]. + pub async fn prepare_packet( + &mut self, + length: usize, + packet_id: Option, + ) -> TxPacket { + self.tx_ring.prepare_packet(length, packet_id).await + } + + /// Wait for an RX or TX interrupt to have + /// occured. + pub async fn rx_or_tx(&mut self) { + let mut polled_once = false; + core::future::poll_fn(|ctx| { + if polled_once { + Poll::Ready(()) + } else { + polled_once = true; + EthernetDMA::rx_waker().register(ctx.waker()); + EthernetDMA::tx_waker().register(ctx.waker()); + Poll::Pending + } + }) + .await; + } +} + +#[cfg(feature = "ptp")] +/// PTP methods. +impl EthernetDMA<'_, '_> { + /// Try to get the timestamp for the given packet ID. + /// + /// This function will attempt to find both RX and TX timestamps, + /// so make sure that the provided packet ID is unique between the two. + pub fn poll_timestamp( + &self, + packet_id: &PacketId, + ) -> Poll, PacketIdNotFound>> { + // Check if it's a TX packet + let tx = self.poll_tx_timestamp(packet_id); + + if tx != Poll::Ready(Err(PacketIdNotFound)) { + return tx; + } + + // It's not a TX packet, check if it's an RX packet + Poll::Ready(self.rx_timestamp(packet_id)) + } + + /// Get the RX timestamp for the given packet ID. + pub fn rx_timestamp( + &self, + packet_id: &PacketId, + ) -> Result, PacketIdNotFound> { + self.rx_ring.timestamp(packet_id) + } + + /// Blockingly wait until the TX timestamp for + /// the given ID is available. + pub fn wait_for_tx_timestamp( + &self, + packet_id: &PacketId, + ) -> Result, PacketIdNotFound> { + self.tx_ring.wait_for_timestamp(packet_id) + } + + /// Poll to check if the TX timestamp for the given + /// ID is available. + pub fn poll_tx_timestamp( + &self, + packet_id: &PacketId, + ) -> Poll, PacketIdNotFound>> { + self.tx_ring.poll_timestamp(packet_id) + } + + /// Get the TX timestamp for the given ID. + #[cfg(feature = "async-await")] + pub async fn tx_timestamp( + &mut self, + packet_id: &PacketId, + ) -> Result, PacketIdNotFound> { + self.tx_ring.timestamp(packet_id).await + } + + /// Get the next packet ID. + pub fn next_packet_id(&mut self) -> PacketId { + let id = PacketId(self.packet_id_counter); + self.packet_id_counter += 1; + id + } +} + +/// A summary of the reasons for the interrupt +/// that occured +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy)] +pub struct InterruptReasonSummary { + /// The interrupt was caused by an RX event. + pub is_rx: bool, + /// The interrupt was caused by an TX event. + pub is_tx: bool, + /// The interrupt was caused by an error event. + pub is_error: bool, } diff --git a/src/ethernet/mod.rs b/src/ethernet/mod.rs index 12240d5f..9a5ea111 100644 --- a/src/ethernet/mod.rs +++ b/src/ethernet/mod.rs @@ -5,6 +5,10 @@ //! - SMSC LAN8742a //! - Micrel KSZ8081R //! +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project +//! //! # Examples //! //! - [Simple link checker for the Nucleo-H743ZI2](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/ethernet-nucleo-h743zi2.rs) @@ -32,6 +36,13 @@ pub trait PHY { mod ksz8081r; mod lan8742a; +pub(crate) mod raw_descriptor; + +mod rx; +pub use rx::{RxDescriptor, RxDescriptorRing, RxError, RxPacket}; + +mod tx; +pub use tx::{TxDescriptor, TxDescriptorRing, TxError}; /// Some common implementations of the [PHY trait](PHY) pub mod phy { @@ -39,9 +50,17 @@ pub mod phy { pub use super::lan8742a::*; } +mod packet_id; +pub use packet_id::PacketId; + +mod cache; +pub(crate) use cache::Cache; + mod eth; -pub use eth::{enable_interrupt, interrupt_handler, new, new_unchecked}; -pub use eth::{DesRing, EthernetDMA, EthernetMAC}; +#[cfg(feature = "ptp")] +pub use eth::PtpFrameWithId; +pub use eth::{eth_interrupt_handler, new, new_unchecked}; +pub use eth::{EthernetDMA, EthernetMAC, Parts}; /// Marks a set of pins used to communciate to a PHY with a Reduced Media /// Independent Interface (RMII) @@ -49,6 +68,10 @@ pub trait PinsRMII { fn set_speed(&mut self, speed: Speed); } +// ***VERIFY THIS!!!!!*** +/// From the datasheet: *VLAN Frame maxsize = 1522* +pub const MTU: usize = 1522; + // Two lanes impl PinsRMII for (REF_CLK, MDIO, MDC, CRS_DV, RXD0, RXD1, TX_EN, TXD0, TXD1) diff --git a/src/ethernet/packet_id.rs b/src/ethernet/packet_id.rs new file mode 100644 index 00000000..99419f1c --- /dev/null +++ b/src/ethernet/packet_id.rs @@ -0,0 +1,46 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +/// A packet ID. +/// +/// This packet ID can be used to obtain information about a specific +/// ethernet frame (either sent or received) from the DMA. +/// + +#[cfg_attr( + feature = "ptp", + doc = " +The main use is obtaining timestamps for frames using [`EthernetDMA::poll_timestamp`](crate::EthernetDMA::poll_timestamp) +" +)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct PacketId(pub u32); + +impl PacketId { + /// The initial value for an [`Option`] + pub const INIT: Option = None; +} + +impl From for PacketId { + fn from(value: u32) -> Self { + Self(value) + } +} + +#[cfg(feature = "ptp")] +impl From for PacketId { + fn from(value: smoltcp::phy::PacketMeta) -> Self { + Self(value.id) + } +} + +#[cfg(feature = "ptp")] +impl From for smoltcp::phy::PacketMeta { + fn from(value: PacketId) -> Self { + let mut meta = smoltcp::phy::PacketMeta::default(); + meta.id = value.0; + meta + } +} diff --git a/src/ethernet/raw_descriptor.rs b/src/ethernet/raw_descriptor.rs new file mode 100644 index 00000000..eb9bab15 --- /dev/null +++ b/src/ethernet/raw_descriptor.rs @@ -0,0 +1,151 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +//TODO remove +// this is raw_descriptor +use volatile_register::{RO, RW}; + +use crate::ethernet::MTU; + +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/ethernet/rx/descriptor.rs b/src/ethernet/rx/descriptor.rs new file mode 100644 index 00000000..8df578ab --- /dev/null +++ b/src/ethernet/rx/descriptor.rs @@ -0,0 +1,241 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +use core::sync::atomic::{self, Ordering}; + +use crate::ethernet::{raw_descriptor::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> { + // Only single-frame descriptors and non-context descriptors are supported + // for now. + if self.has_error() { + Err(RxDescriptorError::DmaError) + } else 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/ethernet/rx/mod.rs b/src/ethernet/rx/mod.rs new file mode 100644 index 00000000..bb40d116 --- /dev/null +++ b/src/ethernet/rx/mod.rs @@ -0,0 +1,367 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +pub use descriptor::RxDescriptor; + +use super::{raw_descriptor::DescriptorRing, PacketId}; +use crate::stm32::ETHERNET_DMA; + +mod descriptor; + +#[cfg(feature = "ptp")] +use crate::{ethernet::eth::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)] +pub enum RxError { + /// The received packet was truncated + Truncated, + /// An error occured with the DMA + DmaError, + /// Receiving would block + WouldBlock, +} + +impl From for RxError { + fn from(value: RxDescriptorError) -> Self { + match value { + RxDescriptorError::Truncated => Self::Truncated, + RxDescriptorError::DmaError => Self::DmaError, + } + } +} + +/// An RX descriptor ring. +pub type RxDescriptorRing<'rx> = DescriptorRing<'rx, RxDescriptor>; + +/// Rx DMA state +pub struct RxRing<'a> { + ring: RxDescriptorRing<'a>, + next_entry: usize, +} + +impl<'a> RxRing<'a> { + /// Allocate + pub(crate) fn new(ring: RxDescriptorRing<'a>) -> Self { + RxRing { + ring, + next_entry: 0, + } + } + + /// Setup the DMA engine (**required**) + pub(crate) fn start(&mut self, eth_dma: ÐERNET_DMA) { + // Setup ring + 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.ring.descriptors_start_address(); + + { + 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(); + } + + /// Stop the RX DMA + pub(crate) fn stop(&self, eth_dma: ÐERNET_DMA) { + 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() {} + } + + /// Demand that the DMA engine polls the current `RxDescriptor` + /// (when in [`RunningState::Stopped`].) + 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() }; + + // On H7, we poll by re-writing the tail pointer register. + eth_dma + .dmacrx_dtpr + .modify(|r, w| unsafe { w.bits(r.bits()) }); + } + + /// 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() }; + + if eth_dma.dmacsr.read().fbe().bit_is_set() { + super::EthernetDMA::panic_fbe(); + } + + let rps = eth_dma.dmadsr.read().rps0().bits(); + + match rps { + // Reset or Stop Receive Command issued + 0b000 => RunningState::Stopped, + // Fetching receive transfer descriptor + 0b001 => RunningState::Running, + // Waiting for receive packet + 0b011 => RunningState::Running, + // Receive descriptor unavailable + 0b100 => RunningState::Stopped, + // Closing receive descriptor + 0b101 => RunningState::Running, + // Transferring the receive packet data from receive buffer to host memory + 0b111 => RunningState::Running, + // Timestamp write state + 0b110 => RunningState::Running, + _ => RunningState::Unknown, + } + } + + /// Check if we can receive a new packet + pub fn next_entry_available(&self) -> bool { + self.ring.descriptor(self.next_entry).is_available() + } + + /// Obtain the index of the packet to receive (if any is ready). + /// + /// 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 { + if !Self::running_state().is_running() { + Self::demand_poll(); + } + + if self.next_entry_available() { + let entries_len = self.ring.len(); + let (desc, buffer) = self.ring.get_mut(self.next_entry); + + desc.recv(packet_id, buffer)?; + + let entry_num = self.next_entry; + self.next_entry = (self.next_entry + 1) % entries_len; + + Ok(entry_num) + } else { + Err(RxError::WouldBlock) + } + } + + fn recv_and_timestamp(&mut self, entry: usize) -> RxPacket { + let entries_len = self.ring.len(); + + 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 = self.recv_next_impl(packet_id)?; + + Ok(self.recv_and_timestamp(entry)) + } + + /// Receive the next packet. + /// + /// The returned [`RxPacket`] can be used as a slice, and + /// will contain the ethernet data. + #[cfg(feature = "async-await")] + pub async fn recv(&mut self, packet_id: Option) -> RxPacket { + let entry = core::future::poll_fn(|ctx| { + let res = self.recv_next_impl(packet_id); + + match res { + Ok(value) => Poll::Ready(value), + Err(_) => { + super::EthernetDMA::rx_waker().register(ctx.waker()); + Poll::Pending + } + } + }) + .await; + + self.recv_and_timestamp(entry) + } +} + +#[cfg(feature = "ptp")] +impl<'a> RxRing<'a> { + /// Get the timestamp for a specific ID + pub fn timestamp( + &self, + id: &PacketId, + ) -> Result, PacketIdNotFound> { + let entry = self.ring.descriptors().find(|e| e.has_packet_id(id)); + + let entry = entry.ok_or(PacketIdNotFound)?; + + 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. + Unknown, + /// The RX DMA is stopped. + Stopped, + /// The RX DMA is running. + Running, +} + +impl RunningState { + /// whether self equals to `RunningState::Running` + pub fn is_running(&self) -> bool { + *self == RunningState::Running + } +} + +/// A received packet. +/// +/// This packet implements [Deref<\[u8\]>](core::ops::Deref) and should be used +/// as a slice. +pub struct RxPacket<'a, 'buf> { + entry: &'a mut RxDescriptor, + buffer: &'buf mut [u8], + length: usize, +} + +impl core::ops::Deref for RxPacket<'_, '_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buffer[..self.length] + } +} + +impl core::ops::DerefMut for RxPacket<'_, '_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer[..self.length] + } +} + +impl Drop for RxPacket<'_, '_> { + fn drop(&mut self) { + self.entry.set_owned(self.buffer); + } +} + +impl RxPacket<'_, '_> { + /// Pass the received packet back to the DMA engine. + pub fn free(self) { + drop(self) + } + + /// Get the timestamp associated with this packet + #[cfg(feature = "ptp")] + pub fn timestamp(&self) -> Option { + self.entry.timestamp() + } +} diff --git a/src/ethernet/tx/descriptor.rs b/src/ethernet/tx/descriptor.rs new file mode 100644 index 00000000..3b4cece1 --- /dev/null +++ b/src/ethernet/tx/descriptor.rs @@ -0,0 +1,254 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +use core::sync::atomic::{self, Ordering}; + +use crate::ethernet::{raw_descriptor::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 { + // TODO remove pub + pub 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/ethernet/tx/mod.rs b/src/ethernet/tx/mod.rs new file mode 100644 index 00000000..df1a2116 --- /dev/null +++ b/src/ethernet/tx/mod.rs @@ -0,0 +1,390 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +use super::{raw_descriptor::DescriptorRing, PacketId}; +use crate::stm32::ETHERNET_DMA; + +#[cfg(feature = "ptp")] +use super::eth::PacketIdNotFound; +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +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 { + /// Ring buffer is full + WouldBlock, +} + +/// Tx DMA state +pub struct TxRing<'a> { + ring: TxDescriptorRing<'a>, + next_entry: usize, +} + +impl<'ring> TxRing<'ring> { + /// Allocate + /// + /// `start()` will be needed before `send()` + pub(crate) fn new(ring: TxDescriptorRing<'ring>) -> Self { + TxRing { + ring, + next_entry: 0, + } + } + + /// Start the Tx DMA engine + pub(crate) fn start(&mut self, eth_dma: ÐERNET_DMA) { + for descriptor in self.ring.descriptors_mut() { + descriptor.setup(); + } + + let ring_ptr = self.ring.descriptors_start_address(); + + { + let tx_descriptor_count = self.ring.descriptors().count(); + assert!(tx_descriptor_count >= 4); + + // Assert that the descriptors are properly aligned. + // + // FIXME: these require different alignment if the data is stored + // in AXI SRAM + assert!(ring_ptr as u32 % 4 == 0); + assert!(self.ring.last_descriptor() as *const _ as u32 % 4 == 0); + + // Set the start pointer. + eth_dma + .dmactx_dlar + .write(|w| unsafe { w.bits(ring_ptr as u32) }); + + // Set the Transmit Descriptor Ring Length + eth_dma.dmactx_rlr.write(|w| { + w.tdrl() + .variant((self.ring.descriptors().count() - 1) as u16) + }); + + // Set the tail pointer + eth_dma.dmactx_dtpr.write(|w| unsafe { + w.bits(self.ring.last_descriptor_mut() as *const _ as u32) + }); + } + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + + // We don't need a compiler fence here because all interactions with `Descriptor` are + // volatiles + + let start_reg = ð_dma.dmactx_cr; + + // Start transmission + start_reg.modify(|_, w| w.st().set_bit()); + } + + /// Stop the TX DMA + pub(crate) fn stop(&self, eth_dma: ÐERNET_DMA) { + 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() {} + } + + 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.entry_available(self.next_entry % self.ring.len()) + } + + /// Check if we can send the next TX entry. + /// + /// If [`Ok(res)`] is returned, the caller of must ensure + /// that [`self.entries[res].send()`](TxRingEntry::send) is called + /// before a new invocation of `send_next_impl`. + fn send_next_impl(&mut self) -> Result { + if self.next_entry_available() { + let entry_num = self.next_entry; + + self.next_entry = (self.next_entry + 1) % self.ring.len(); + Ok(entry_num) + } else { + Err(TxError::WouldBlock) + } + } + + /// Prepare a packet for sending. + /// + /// Write the data that you wish to send to the buffer + /// represented by the returned [`TxPacket`] by using it + /// as a slice. + /// + /// When all data is copied into the TX buffer, use [`TxPacket::send()`] + /// to transmit it. + pub fn send_next( + &mut self, + length: usize, + packet_id: Option, + ) -> Result { + let entry = self.send_next_impl()?; + + let (desc, tx_buffer) = self.ring.get_mut(entry); + + assert!(length <= tx_buffer.len(), "Not enough space in TX buffer"); + + Ok(TxPacket { + desc, + buffer: tx_buffer, + length, + packet_id, + }) + } + + /// Prepare a packet for sending. + /// + /// Write the data that you wish to send to the buffer + /// represented by the returned [`TxPacket`] by using it + /// as a slice. + /// + /// When all data is copied into the TX buffer, use [`TxPacket::send()`] + /// to transmit it. + #[cfg(feature = "async-await")] + pub async fn prepare_packet<'tx>( + &'tx mut self, + length: usize, + packet_id: Option, + ) -> TxPacket { + let entry = core::future::poll_fn(|ctx| match self.send_next_impl() { + Ok(packet) => Poll::Ready(packet), + Err(_) => { + super::EthernetDMA::tx_waker().register(ctx.waker()); + Poll::Pending + } + }) + .await; + + let (desc, tx_buffer) = self.ring.get_mut(entry); + + assert!(length <= tx_buffer.len(), "Not enough space in TX buffer"); + + TxPacket { + desc, + buffer: tx_buffer, + length, + packet_id, + } + } + + /// Demand that the DMA engine polls the current `TxDescriptor` + /// (when we just transferred ownership to the hardware). + 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() }; + + // 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()) }); + } + + /// Is the Tx DMA engine running? + pub fn is_running() -> bool { + Self::running_state().is_running() + } + + /// 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() }; + + if eth_dma.dmacsr.read().fbe().bit_is_set() { + super::EthernetDMA::panic_fbe(); + } + + let tx_status = eth_dma.dmadsr.read().tps0().bits(); + + match tx_status { + // Reset or Stop Transmit Command issued + 0b000 => RunningState::Stopped, + // Fetching transmit transfer descriptor + 0b001 => RunningState::Running, + // Waiting for status + 0b010 => RunningState::Running, + // Reading Data from host memory buffer and queuing it to transmit buffer + 0b011 => RunningState::Running, + + 0b101 => RunningState::Reserved, + + // Transmit descriptor unavailable + 0b110 => RunningState::Suspended, + // Timestamp write + 0b100 => RunningState::Running, + // Closing Tx descriptor + 0b111 => RunningState::Running, + + _ => RunningState::Unknown, + } + } +} + +#[cfg(feature = "ptp")] +impl TxRing<'_> { + fn entry_for_id(&self, id: &PacketId) -> Option { + 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.ring.descriptor(index).timestamp() + } + + /// Blockingly wait untill the timestamp for the + /// given ID is available. + pub fn wait_for_timestamp( + &self, + packet_id: &PacketId, + ) -> Result, PacketIdNotFound> { + loop { + if let Poll::Ready(res) = self.poll_timestamp(packet_id) { + return res; + } + } + } + + /// Poll to check if the timestamp for the given ID is already + /// available. + pub fn poll_timestamp( + &self, + packet_id: &PacketId, + ) -> Poll, PacketIdNotFound>> { + let entry = if let Some(entry) = self.entry_for_id(packet_id) { + entry + } else { + return Poll::Ready(Err(PacketIdNotFound)); + }; + + if self.entry_available(entry) { + let time = self.entry_timestamp(entry); + Poll::Ready(Ok(time)) + } else { + Poll::Pending + } + } + + /// Wait until the timestamp for the given ID is available. + #[cfg(feature = "async-await")] + pub async fn timestamp( + &mut self, + packet_id: &PacketId, + ) -> Result, PacketIdNotFound> { + core::future::poll_fn(move |ctx| { + let res = self.poll_timestamp(packet_id); + if res.is_pending() { + super::EthernetDMA::tx_waker().register(ctx.waker()); + } + res + }) + .await + } +} + +#[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 + Stopped, + /// Fetching transmit transfer descriptor; + /// Waiting for status; + /// Reading Data from host memory buffer and queuing it to transmit buffer + Running, + /// Reserved for future use + Reserved, + /// Transmit descriptor unavailable + Suspended, + /// Invalid value + Unknown, +} + +impl RunningState { + /// Check whether this state represents that the + /// TX DMA is running + pub fn is_running(&self) -> bool { + *self == RunningState::Running + } +} + +/// A struct that represents a soon-to-be-sent packet. +/// +/// Implements [`Deref`] and [`DerefMut`] with `[u8]` as a target +/// so it can be used as a slice. +/// +/// [`Deref`]: core::ops::Deref +/// [`DerefMut`]: core::ops::DerefMut +pub struct TxPacket<'borrow> { + desc: &'borrow mut TxDescriptor, + buffer: &'borrow mut [u8], + length: usize, + packet_id: Option, +} + +impl core::ops::Deref for TxPacket<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buffer[..self.length] + } +} + +impl core::ops::DerefMut for TxPacket<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer[..self.length] + } +} + +impl TxPacket<'_> { + /// Send this packet! + pub fn send(self) { + drop(self); + } +} + +impl Drop for TxPacket<'_> { + fn drop(&mut self) { + self.desc.send(self.packet_id, &self.buffer[..self.length]); + TxRing::demand_poll(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 054a7f65..1904f8b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,15 @@ pub use crate::stm32 as device; #[cfg_attr(docsrs, doc(cfg(feature = "rt")))] pub use crate::stm32::interrupt; +#[cfg(all( + feature = "device-selected", + feature = "ethernet", + not(feature = "rm0455"), + feature = "ptp" +))] +#[cfg_attr(docsrs, doc(cfg(feature = "ethernet")))] +pub mod ptp; + #[cfg(feature = "device-selected")] pub mod adc; #[cfg(all(feature = "device-selected", feature = "can"))] diff --git a/src/ptp/mod.rs b/src/ptp/mod.rs new file mode 100644 index 00000000..34f90fdf --- /dev/null +++ b/src/ptp/mod.rs @@ -0,0 +1,389 @@ +//! PTP access and configuration. +//! +//! See [`EthernetPTP`] for a more details. +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +use crate::ethernet::EthernetDMA; +use crate::rcc::CoreClocks; + +mod timestamp; +pub use timestamp::Timestamp; + +#[cfg(all(not(feature = "stm32f1xx-hal"), feature = "async-await"))] +use {core::task::Poll, futures::task::AtomicWaker}; + +mod subseconds; +pub use subseconds::{ + Subseconds, NANOS_PER_SECOND, SUBSECONDS_PER_SECOND, SUBSECONDS_TO_SECONDS, +}; + +/// Access to the IEEE 1508v2 PTP peripheral present on the ethernet peripheral. +/// +/// On STM32FXXX's, the PTP peripheral has/uses the following important parts: +/// * HCLK (the chip's high speed clock, configured externally). +/// * The global timestamp (`global_time`, a [`Timestamp`]). +/// * A subsecond increment register (`subsecond_increment`, a [`Subseconds`] with a value of 0 to 255, see [`EthernetPTP::subsecond_increment`]). +/// * An accumulator register (`accumulator`, an [`u32`]). +/// * An addend register (`addend`, an [`u32`], see [`EthernetPTP::addend`] and [`EthernetPTP::set_addend`]). +/// +/// To ensure that `global_time` advances at the correct rate, the system performs the following steps: +/// 1. On every clock of HCLK, `addend` is added to `accumulator`. +/// 2. If `accumulator` overflows during step 1, add `subsecond_increment` to `global_time`. +/// +/// When a new [`EthernetPTP`] is created, it is assumed that the frequency of HCLK is exactly correct. +/// Using HCLK, values for `subsecond_increment` and `addend` are calculated so that `global_time` represents +/// real-time. +/// +/// Subsequently, `addend` can be adjusted to compensate for possible errors in HCLK, using [`EthernetPTP::addend`] and [`EthernetPTP::set_addend`] +/// +/// To assess the correctness of the current speed at which `global_time` is running, one can use the +/// following equation: +/// +/// ```no_compile +/// clock_ratio = ((2^31 / subsecond_increment) / (HCLK_HZ * (addend / 2^32))) +/// ``` +/// Values greater than 1 indicate that the provided `HCLK_HZ` is less than the actual frequency of HCLK, which should +/// be compensated by increasing `addend`. Values less than 1 indicate that the provided `HCLK_HZ` is greater than the +/// actual frequency of HCLK, which should be compensated by decreasing `addend`. +/// +/// [`NonZeroU8`]: core::num::NonZeroU8 +pub struct EthernetPTP {} + +impl EthernetPTP { + /// # 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::stm32::ETHERNET_MAC::ptr() + } + + // Calculate the `addend` required for running `global_time` at + // the correct rate + const fn calculate_regs(hclk: u32) -> (Subseconds, u32) { + let half_hclk = hclk / 2; + + // Calculate the closest `subsecond_increment` we can use if we want to update at a + // frequency of `half_hclk` + let stssi = Subseconds::nearest_increment(half_hclk); + let half_rate_subsec_increment_hz = stssi.hertz(); + + // Calculate the `addend` required for running `global_time` at + // the correct rate, given that we increment `global_time` by `stssi` every + // time `accumulator` overflows. + let tsa = ((half_rate_subsec_increment_hz as u64 * u32::MAX as u64) + / hclk as u64) as u32; + (stssi, tsa) + } + + pub(crate) fn new( + clocks: CoreClocks, + // 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 { + let hclk = clocks.hclk().to_Hz(); + + let (stssi, tsa) = Self::calculate_regs(hclk); + + 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(0, 0); + + me + } + + /// Get the configured subsecond increment. + pub fn subsecond_increment(&self) -> Subseconds { + // 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 { + // 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) { + { + // 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, seconds: u32, nanoseconds: u32) { + { + // 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(nanoseconds) }); + + 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. + /// + /// If `time` is negative, it will instead be subtracted from the + /// system time. + pub fn update_time(&mut self, time: Timestamp) { + let seconds = time.seconds(); + let subseconds = time.subseconds_signed(); + + { + // SAFETY: we only write to `mactscr` (timestamp control register), `macstsur` + // (timestamp update seconds register) and `macstnur` (timestmap update subsecond/nanosecond + // register) + let (mactscr, macstsur, macstnur) = unsafe { + let mac = Self::mac(); + (&mac.mactscr, &mac.macstsur, &mac.macstnur) + }; + + macstsur.write(|w| unsafe { w.bits(seconds) }); + macstnur.write(|w| unsafe { w.bits(subseconds) }); + + while mactscr.read().tsupdt().bit_is_set() {} + mactscr.modify(|_, w| w.tsupdt().set_bit()); + while mactscr.read().tsupdt().bit_is_set() {} + } + } + + /// Get the current time + pub fn now() -> Timestamp { + Self::get_time() + } + + /// Get the current time. + pub fn get_time() -> Timestamp { + let try_read_time = || { + 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)) + } else { + Err(()) + } + }; + + loop { + if let Ok(res) = try_read_time() { + return res; + } + } + } + + // /// Enable the PPS output on the provided pin. + // pub fn enable_pps

(&mut self, pin: P) -> P::Output + // where + // P: PPSPin, + // { + // pin.enable() + // } +} + +/// Setting and configuring target time interrupts on the STM32F107 does not +/// make any sense: we can generate the interrupt, but it is impossible to +/// clear the flag as the register required to do so does not exist. +impl EthernetPTP { + #[cfg(feature = "async-await")] + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + + /// Configure the target time. + fn set_target_time(&mut self, timestamp: Timestamp) { + let (high, low) = (timestamp.seconds(), timestamp.subseconds_signed()); + + { + // 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. + /// + /// You must call [`EthernetPTP::interrupt_handler`] in the `ETH` + /// interrupt to detect (and clear) the correct status bits. + pub fn configure_target_time_interrupt(&mut self, timestamp: Timestamp) { + self.set_target_time(timestamp); + } + + /// 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()) + || (EthernetPTP::now().raw() >= timestamp.raw()) + { + Poll::Ready(()) + } else { + EthernetPTP::waker().register(ctx.waker()); + Poll::Pending + } + }) + .await; + } + + #[inline(always)] + fn read_and_clear_interrupt_flag() -> bool { + { + // 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. + /// + /// Returns a boolean indicating whether or not the interrupt + /// was caused by a Timestamp trigger and clears the interrupt + /// flag. + pub fn interrupt_handler() -> bool { + 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 is_tsint { + if let Some(waker) = EthernetPTP::waker().take() { + waker.wake(); + } else { + EthernetPTP::read_and_clear_interrupt_flag(); + } + } + + #[cfg(not(feature = "async-await"))] + EthernetPTP::read_and_clear_interrupt_flag(); + + is_tsint + } + + /// Configure the PPS output frequency. + /// + /// The PPS output frequency becomes `2 ^ pps_freq`. `pps_freq` is + /// clamped to `[0..31]`. + pub fn set_pps_freq(&mut self, pps_freq: u8) { + let pps_freq = pps_freq.max(31); + + // 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. + { + // 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)); + } + } +} + +#[cfg(all(test, not(target_os = "none")))] +mod test { + + use super::*; + + // Test that we get accurate addend and subsecond_increment values + // with the provided clock speeds. + #[test] + fn hclk_to_regs() { + for hclk_hz in (25..180).map(|v| v * 1_000_000) { + let (stssi, tsa) = EthernetPTP::calculate_regs(hclk_hz); + + let stssi = stssi.raw() as f64; + let tsa = tsa as f64; + + // calculate the clock ratio + let clock_ratio = (SUBSECONDS_PER_SECOND as f64 / stssi) + / (hclk_hz as f64 * (tsa / 0xFFFF_FFFFu32 as f64)); + + let ppm = (clock_ratio - 1f64) * 1_000_000f64; + + assert!(ppm <= 0.06, "{} at {}", ppm, hclk_hz); + } + } +} diff --git a/src/ptp/subseconds.rs b/src/ptp/subseconds.rs new file mode 100644 index 00000000..74df5e93 --- /dev/null +++ b/src/ptp/subseconds.rs @@ -0,0 +1,174 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +/// The amount of nanoseconds per second. +pub const NANOS_PER_SECOND: u32 = 1_000_000_000; + +/// The amount of subseconds per second. +pub const SUBSECONDS_PER_SECOND: u32 = 0x7FFF_FFFF; + +/// The ratio to use to convert subseconds to seconds. +pub const SUBSECONDS_TO_SECONDS: f32 = 1.0 / (SUBSECONDS_PER_SECOND as f32); + +const NS_PER_S: u64 = NANOS_PER_SECOND as u64; +const SUBS_PER_S: u64 = SUBSECONDS_PER_SECOND as u64; + +/// A subsecond value as produced by the PTP peripheral +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Subseconds(u32); + +impl Subseconds { + /// The maximum possible value for [`Subseconds`] + pub const MAX_VALUE: u32 = SUBSECONDS_PER_SECOND; + + /// The maximum possible [`Subseconds`] + pub const MAX: Self = Self(SUBSECONDS_PER_SECOND); + + /// Zero [`Subseconds`] + pub const ZERO: Self = Self(0); + + /// Create a new [`Subseconds`] from the provided value. + /// + /// The returned [`Subseconds`] represents a time of `value / 2^31` seconds. + /// + /// To obtain that representation in nanoseconds, see [`Subseconds::nanos`]. + /// + /// To approximate a [`Subseconds`] from nanoseconds, see [`Subseconds::new_from_nanos`]. + /// + /// Returns `None` if `value > SUBSECONDS_PER_SECOND`. (See [`SUBSECONDS_PER_SECOND`]). + pub const fn new(value: u32) -> Option { + if value > SUBSECONDS_PER_SECOND { + None + } else { + Some(Self(value)) + } + } + + /// Create a new [`Subseconds`] from the provided value, without verifying that `value` + /// is less than or equal to [`Self::MAX_VALUE`]). + /// + /// The returned [`Subseconds`] represents a time of `value / 2^31` seconds. + /// + /// To obtain that representation in nanoseconds, see [`Subseconds::nanos`]. + /// + /// To approximate a [`Subseconds`] from nanoseconds, see [`Subseconds::new_from_nanos`] + pub(crate) const fn new_unchecked(value: u32) -> Self { + Self(value) + } + + /// Create a new [`Subseconds`] from the given amount of nanoseconds, + /// using a round-to-nearest method. + /// + /// Returns [`None`] if `nanos >= NANOS_PER_SECOND`. (See [`NANOS_PER_SECOND`]) + pub const fn new_from_nanos(nanos: u32) -> Option { + if nanos >= NANOS_PER_SECOND { + return None; + } + + let subseconds = + ((nanos as u64 * SUBS_PER_S) + (NS_PER_S / 2)) / NS_PER_S; + + Some(Subseconds::new_unchecked(subseconds as u32)) + } + + /// Convert this [`Subseconds`] to nanoseconds, using a round-to-nearest method. + pub const fn nanos(&self) -> u32 { + let nanos = + ((self.0 as u64 * NS_PER_S) + (SUBS_PER_S / 2)) / SUBS_PER_S; + + nanos as u32 + } + + /// Get the raw value of this [`Subseconds`] + pub const fn raw(&self) -> u32 { + self.0 + } + + #[allow(unused)] + /// Convert this [`Subseconds`] to Hertz + pub(crate) const fn hertz(&self) -> u32 { + SUBSECONDS_PER_SECOND / self.0 + } + + #[allow(unused)] + pub(crate) const fn nearest_increment(input_clk_hz: u32) -> Subseconds { + let hclk_half_subs = + (SUBSECONDS_PER_SECOND + (input_clk_hz / 2)) / input_clk_hz; + + Self::new_unchecked(hclk_half_subs) + } +} + +impl core::ops::Add for Subseconds { + type Output = Self; + + fn add(self, rhs: Subseconds) -> Self::Output { + Self(self.0.wrapping_add(rhs.0) % (SUBSECONDS_PER_SECOND + 1)) + } +} + +impl core::ops::AddAssign for Subseconds { + fn add_assign(&mut self, rhs: Subseconds) { + *self = *self + rhs; + } +} + +impl core::ops::Sub for Subseconds { + type Output = Self; + + fn sub(self, rhs: Subseconds) -> Self::Output { + Self(self.0.wrapping_sub(rhs.0) % (SUBSECONDS_PER_SECOND + 1)) + } +} + +impl core::ops::SubAssign for Subseconds { + fn sub_assign(&mut self, rhs: Subseconds) { + *self = *self - rhs; + } +} + +#[cfg(all(test, not(target_os = "none")))] +mod test { + + use super::*; + + // Assert that values produced by [`Subseconds::nearest_increment`] for some + // valid frequencies are within the correct span for `stssi` + #[test] + fn correct_subsecond_increment() { + for i in (25_000..180_000).map(|v| v * 1_000) { + let subs = Subseconds::nearest_increment(i).raw(); + assert!(subs > 0 && subs <= 255); + } + } + + #[test] + fn from_nanos() { + for i in [0, 1, 2, 3, NANOS_PER_SECOND - 1] { + let subseconds = Subseconds::new_from_nanos(i).unwrap(); + assert!(subseconds.raw() < SUBSECONDS_PER_SECOND); + } + + assert!(Subseconds::new_from_nanos(NANOS_PER_SECOND).is_none()); + assert!(Subseconds::new_from_nanos(u32::MAX).is_none()); + } + + #[test] + fn subsecond_math() { + let one = Subseconds::new(1).unwrap(); + let two = Subseconds::new(2).unwrap(); + let three = Subseconds::new(3).unwrap(); + let max = Subseconds::new(SUBSECONDS_PER_SECOND).unwrap(); + let zero = Subseconds::new(0).unwrap(); + + assert_eq!(one + two, three); + assert_eq!(two - one, one); + + assert_eq!(one - max + max, one); + assert_eq!(one - two, max); + assert_eq!(one + max, zero); + assert_eq!(two + max, one); + } +} diff --git a/src/ptp/timestamp.rs b/src/ptp/timestamp.rs new file mode 100644 index 00000000..0bca05c5 --- /dev/null +++ b/src/ptp/timestamp.rs @@ -0,0 +1,239 @@ +//! This implementation is derived from 0BSD-relicensed work done by +//! Johannes Draaijer for the +//! [`stm32-eth`](https://github.com/stm32-rs/stm32-eth) project + +use super::{Subseconds, NANOS_PER_SECOND}; + +/// A timestamp produced by the PTP periperhal +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Timestamp(i64); + +#[cfg(feature = "defmt")] +impl defmt::Format for Timestamp { + fn format(&self, fmt: defmt::Formatter) { + if self.is_positive() { + defmt::write!(fmt, "{}.{:09}", self.seconds(), self.nanos()); + } else { + defmt::write!(fmt, "-{}.{:09}", self.seconds(), self.nanos()); + } + } +} + +impl Timestamp { + // The bit that represents the signedness of the timestamp in the + // subseconds value. + const SIGN_BIT: u32 = 0x8000_0000; + + /// Create a new [`Timestamp`] + pub const fn new( + negative: bool, + seconds: u32, + subseconds: Subseconds, + ) -> Self { + Self::new_unchecked(negative, seconds, subseconds.raw()) + } + + /// Create a new [`Timestamp`] from the given raw value. + pub const fn new_raw(value: i64) -> Self { + Self(value) + } + + /// Get the raw value of this [`Timestamp`] + pub const fn raw(&self) -> i64 { + self.0 + } + + /// Check whether this timestamp is negative or not. + pub const fn is_negative(&self) -> bool { + self.0.is_negative() + } + + /// Check whether this timestamp is positive or not. + pub const fn is_positive(&self) -> bool { + !self.is_negative() + } + + pub(crate) const fn new_unchecked( + negative: bool, + seconds: u32, + subseconds: u32, + ) -> Self { + let seconds: i64 = (seconds as i64) << 31; + let subseconds: i64 = subseconds as i64; + + let mut total = seconds + subseconds; + + if negative { + total = -total; + }; + + Self(total) + } + + /// Get the second component of this timestamp + pub const fn seconds(&self) -> u32 { + (self.0.abs() >> 31) as u32 + } + + /// Get the raw subsecond value of this timestamp. + pub const fn subseconds(&self) -> Subseconds { + Subseconds::new_unchecked( + self.0.unsigned_abs() as u32 & Subseconds::MAX_VALUE, + ) + } + + /// Get the signed subsecond value of this timestamp. + /// + /// Note that this is _not_ an i32: it is, technically, + /// a u31 with a leading sign bit. + pub const fn subseconds_signed(&self) -> u32 { + let mut subseconds = self.subseconds().raw(); + + if self.0.is_negative() { + subseconds |= Self::SIGN_BIT; + } + + subseconds + } + + /// Get the nanosecond component of this timestamp + pub const fn nanos(&self) -> u32 { + self.subseconds().nanos() + } + + /// Get the total amount of nanoseconds in this [`Timestamp`]. + /// + /// Example: + /// ```rust + /// # use stm32_eth::ptp::{Subseconds, Timestamp}; + /// let timestamp = Timestamp::new(false, 500, Subseconds::new_from_nanos(500_000).unwrap()); + /// assert_eq!(timestamp.total_nanos(), 500 * 1_000_000_000 + 500_000); + /// + /// + /// let timestamp_neg = Timestamp::new(true, 500, Subseconds::new_from_nanos(500_000).unwrap()); + /// assert_eq!(timestamp_neg.total_nanos(), -1 * (500 * 1_000_000_000 + 500_000)); + /// ``` + pub const fn total_abs_nanos(&self) -> u64 { + self.seconds() as u64 * NANOS_PER_SECOND as u64 + self.nanos() as u64 + } + + /// Create a new timestamp from the provided register values. + pub const fn from_parts(high: u32, low: u32) -> Timestamp { + let negative = (low & Self::SIGN_BIT) == Self::SIGN_BIT; + let subseconds = low & !(Self::SIGN_BIT); + + Timestamp::new_unchecked(negative, high, subseconds) + } +} + +impl core::ops::Add for Timestamp { + type Output = Self; + + fn add(self, rhs: Timestamp) -> Self::Output { + Self(self.0.saturating_add(rhs.0)) + } +} + +impl core::ops::AddAssign for Timestamp { + fn add_assign(&mut self, rhs: Timestamp) { + self.0 += rhs.0; + } +} + +impl core::ops::Sub for Timestamp { + type Output = Self; + + fn sub(self, rhs: Timestamp) -> Self::Output { + Self(self.0.saturating_sub(rhs.0)) + } +} + +impl core::ops::SubAssign for Timestamp { + fn sub_assign(&mut self, rhs: Timestamp) { + self.0 -= rhs.0 + } +} + +#[cfg(all(test, not(target_os = "none")))] +mod test { + use crate::ptp::SUBSECONDS_PER_SECOND; + + use super::{Subseconds, Timestamp}; + + fn subs(val: u32) -> Subseconds { + Subseconds::new(val).unwrap() + } + + #[test] + fn timestamp_add() { + let one = Timestamp::new(false, 1, subs(1)); + let one_big = Timestamp::new(false, 1, subs(SUBSECONDS_PER_SECOND - 1)); + let two = Timestamp::new(false, 2, subs(2)); + let three = Timestamp::new(false, 3, subs(3)); + + let one_neg = Timestamp::new(true, 1, subs(1)); + let one_big_neg = + Timestamp::new(true, 1, subs(SUBSECONDS_PER_SECOND - 1)); + let two_neg = Timestamp::new(true, 2, subs(2)); + let three_neg = Timestamp::new(true, 3, subs(3)); + + let one_minus_two = Timestamp::new(true, 1, subs(1)); + let one_big_plus_two = Timestamp::new(false, 4, subs(0)); + let two_minus_one_big = Timestamp::new(false, 0, subs(4)); + let one_big_neg_plus_two_neg = Timestamp::new(true, 4, subs(0)); + + // +self + +rhs + assert_eq!(one + two, three); + assert_eq!(two + one, three); + assert_eq!(one_big + two, one_big_plus_two); + assert_eq!(two + one_big, one_big_plus_two); + + // +self + -rhs + assert_eq!(one + two_neg, one_minus_two); + assert_eq!(two + one_big_neg, two_minus_one_big); + + // -self + rhs + assert_eq!(one_neg + two, one); + assert_eq!(two + one_neg, one); + + // -self + -rhs + assert_eq!(one_neg + two_neg, three_neg); + assert_eq!(two_neg + one_neg, three_neg); + assert_eq!(one_big_neg + two_neg, one_big_neg_plus_two_neg); + assert_eq!(two_neg + one_big_neg, one_big_neg_plus_two_neg); + } + + #[test] + fn timestamp_sub() { + let one = Timestamp::new(false, 1, subs(1)); + let one_big = Timestamp::new(false, 1, subs(SUBSECONDS_PER_SECOND - 1)); + let two = Timestamp::new(false, 2, subs(2)); + let three = Timestamp::new(false, 3, subs(3)); + + let one_neg = Timestamp::new(true, 1, subs(1)); + let two_neg = Timestamp::new(true, 2, subs(2)); + let three_neg = Timestamp::new(true, 3, subs(3)); + + let one_minus_two = Timestamp::new(true, 1, subs(1)); + let one_minus_one_big = + Timestamp::new(true, 0, subs(SUBSECONDS_PER_SECOND - 2)); + + assert_eq!(one - one_big, one_minus_one_big); + + // +self - +rhs + assert_eq!(two - one, one); + assert_eq!(one - two, one_minus_two); + + // +self - -rhs + assert_eq!(two - one_neg, three); + assert_eq!(one_neg - two, three_neg); + + // -self - +rhs + assert_eq!(one_neg - two, three_neg); + assert_eq!(two - one_neg, three); + + // -self - -rhs + assert_eq!(one_neg - two_neg, one); + assert_eq!(two_neg - one_neg, one_minus_two); + } +}