diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b06463..155a0460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ A pure Rust EtherCAT MainDevice supporting std and no_std environments. - **(breaking)** [#298](https://github.com/ethercrab-rs/ethercrab/pull/298) Change MSRV from 1.81 to 1.85, migrate to edition 2024. +- **(breaking)** [#331](https://github.com/ethercrab-rs/ethercrab/pull/331) (@theol0403) Use + `lock_api` traits to allow for different locking behaviour. - [#301](https://github.com/ethercrab-rs/ethercrab/pull/301) No longer warn when mailbox counter is not what was sent by the MainDevice. - **(breaking)** [#320](https://github.com/ethercrab-rs/ethercrab/pull/320) (@fpdotmonkey) Add diff --git a/Cargo.toml b/Cargo.toml index 573ba05c..afbfe1f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,9 @@ sealed = "0.6.0" serde = { version = "1.0.190", features = ["derive"], optional = true } smlang = "0.8.0" ethercrab-wire = { version = "0.2.0", path = "./ethercrab-wire" } -spin = { version = "0.10.0", default-features = false, features = ["rwlock"] } +spin = { version = "0.10.0", default-features = false, features = ["lock_api", "rwlock"] } crc = { version = "3.2.1", default-features = false } +lock_api = "0.4.13" [target.'cfg(target_os = "windows")'.dependencies] pnet_datalink = { version = "0.35.0", features = ["std"], optional = true } diff --git a/src/lib.rs b/src/lib.rs index be5c1945..bd97cb90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,9 +214,9 @@ const MAINDEVICE_ADDR: EthernetAddress = EthernetAddress([0x10, 0x10, 0x10, 0x10 const BASE_SUBDEVICE_ADDRESS: u16 = 0x1000; #[cfg(feature = "std")] -type SpinStrategy = spin::Yield; +type DefaultLock = spin::rwlock::RwLock<(), spin::Yield>; #[cfg(not(feature = "std"))] -type SpinStrategy = spin::Spin; +type DefaultLock = spin::rwlock::RwLock<(), spin::Spin>; #[allow(unused)] fn test_logger() { diff --git a/src/maindevice.rs b/src/maindevice.rs index f6095977..a29146fe 100644 --- a/src/maindevice.rs +++ b/src/maindevice.rs @@ -402,7 +402,10 @@ impl<'sto> MainDevice<'sto> { pub async fn init_single_group( &self, now: impl Fn() -> u64 + Copy, - ) -> Result, Error> { + ) -> Result< + SubDeviceGroup, + Error, + > { self.init::(now, Default::default(), |group, _subdevice| Ok(group)) .await } diff --git a/src/subdevice/pdi.rs b/src/subdevice/pdi.rs index bf103b59..14162177 100644 --- a/src/subdevice/pdi.rs +++ b/src/subdevice/pdi.rs @@ -4,14 +4,15 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut, Range}, }; +use lock_api::{RawRwLock, RwLock, RwLockReadGuard, RwLockWriteGuard}; -pub struct PdiReadGuard<'a, const N: usize> { - lock: spin::RwLockReadGuard<'a, MySyncUnsafeCell<[u8; N]>>, +pub struct PdiReadGuard<'a, const N: usize, R: RawRwLock> { + lock: RwLockReadGuard<'a, R, MySyncUnsafeCell<[u8; N]>>, range: Range, _lt: PhantomData<&'a ()>, } -impl Deref for PdiReadGuard<'_, N> { +impl Deref for PdiReadGuard<'_, N, R> { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -21,13 +22,13 @@ impl Deref for PdiReadGuard<'_, N> { } } -pub struct PdiIoRawReadGuard<'a, const N: usize> { - lock: spin::RwLockReadGuard<'a, MySyncUnsafeCell<[u8; N]>>, +pub struct PdiIoRawReadGuard<'a, const N: usize, R: RawRwLock> { + lock: RwLockReadGuard<'a, R, MySyncUnsafeCell<[u8; N]>>, ranges: IoRanges, _lt: PhantomData<&'a ()>, } -impl PdiIoRawReadGuard<'_, N> { +impl PdiIoRawReadGuard<'_, N, R> { pub fn inputs(&self) -> &[u8] { let all = unsafe { &*self.lock.get() }.as_slice(); @@ -41,13 +42,13 @@ impl PdiIoRawReadGuard<'_, N> { } } -pub struct PdiIoRawWriteGuard<'a, const N: usize> { - lock: spin::rwlock::RwLockWriteGuard<'a, MySyncUnsafeCell<[u8; N]>, crate::SpinStrategy>, +pub struct PdiIoRawWriteGuard<'a, const N: usize, R: RawRwLock> { + lock: RwLockWriteGuard<'a, R, MySyncUnsafeCell<[u8; N]>>, ranges: IoRanges, _lt: PhantomData<&'a ()>, } -impl PdiIoRawWriteGuard<'_, N> { +impl PdiIoRawWriteGuard<'_, N, R> { pub fn inputs(&self) -> &[u8] { let all = unsafe { &*self.lock.get() }.as_slice(); @@ -61,13 +62,13 @@ impl PdiIoRawWriteGuard<'_, N> { } } -pub struct PdiWriteGuard<'a, const N: usize> { - lock: spin::rwlock::RwLockWriteGuard<'a, MySyncUnsafeCell<[u8; N]>, crate::SpinStrategy>, +pub struct PdiWriteGuard<'a, const N: usize, R: RawRwLock> { + lock: RwLockWriteGuard<'a, R, MySyncUnsafeCell<[u8; N]>>, range: Range, _lt: PhantomData<&'a ()>, } -impl Deref for PdiWriteGuard<'_, N> { +impl Deref for PdiWriteGuard<'_, N, R> { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -77,7 +78,7 @@ impl Deref for PdiWriteGuard<'_, N> { } } -impl DerefMut for PdiWriteGuard<'_, N> { +impl DerefMut for PdiWriteGuard<'_, N, R> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.lock.get_mut()[self.range.clone()] } @@ -87,15 +88,15 @@ impl DerefMut for PdiWriteGuard<'_, N> { /// /// Used in conjunction with [`SubDeviceRef`]. #[doc(alias = "SlavePdi")] -pub struct SubDevicePdi<'group, const MAX_PDI: usize> { +pub struct SubDevicePdi<'group, const MAX_PDI: usize, R: RawRwLock> { subdevice: &'group SubDevice, - pdi: &'group spin::rwlock::RwLock, crate::SpinStrategy>, + pdi: &'group RwLock>, } -unsafe impl Send for SubDevicePdi<'_, MAX_PDI> {} -unsafe impl Sync for SubDevicePdi<'_, MAX_PDI> {} +unsafe impl Send for SubDevicePdi<'_, MAX_PDI, R> {} +unsafe impl Sync for SubDevicePdi<'_, MAX_PDI, R> {} -impl Deref for SubDevicePdi<'_, MAX_PDI> { +impl Deref for SubDevicePdi<'_, MAX_PDI, R> { type Target = SubDevice; fn deref(&self) -> &Self::Target { @@ -103,17 +104,17 @@ impl Deref for SubDevicePdi<'_, MAX_PDI> { } } -impl<'group, const MAX_PDI: usize> SubDevicePdi<'group, MAX_PDI> { +impl<'group, const MAX_PDI: usize, R: RawRwLock> SubDevicePdi<'group, MAX_PDI, R> { pub(crate) fn new( subdevice: &'group SubDevice, - pdi: &'group spin::rwlock::RwLock, crate::SpinStrategy>, + pdi: &'group RwLock>, ) -> Self { Self { subdevice, pdi } } } /// Methods used when a SubDevice is part of a group and part of the PDI has been mapped to it. -impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { +impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI, R>> { /// Get a reference to the raw inputs and outputs for this SubDevice in the Process Data Image /// (PDI). The inputs are read-only, while the outputs can be mutated. /// @@ -136,7 +137,7 @@ impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { /// io.outputs()[0] = 0xaa; /// # } /// ``` - pub fn io_raw_mut(&self) -> PdiIoRawWriteGuard<'_, MAX_PDI> { + pub fn io_raw_mut(&self) -> PdiIoRawWriteGuard<'_, MAX_PDI, R> { PdiIoRawWriteGuard { lock: self.state.pdi.write(), ranges: self.state.config.io.clone(), @@ -175,7 +176,7 @@ impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { /// dbg!(io.outputs()[0]); /// # } /// ``` - pub fn io_raw(&self) -> PdiIoRawReadGuard<'_, MAX_PDI> { + pub fn io_raw(&self) -> PdiIoRawReadGuard<'_, MAX_PDI, R> { PdiIoRawReadGuard { lock: self.state.pdi.read(), ranges: self.state.config.io.clone(), @@ -184,7 +185,7 @@ impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { } /// Get a reference to the raw input data for this SubDevice in the Process Data Image (PDI). - pub fn inputs_raw(&self) -> PdiReadGuard<'_, MAX_PDI> { + pub fn inputs_raw(&self) -> PdiReadGuard<'_, MAX_PDI, R> { PdiReadGuard { lock: self.state.pdi.read(), range: self.state.config.io.input.bytes.clone(), @@ -193,7 +194,7 @@ impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { } /// Get a reference to the raw output data for this SubDevice in the Process Data Image (PDI). - pub fn outputs_raw(&self) -> PdiReadGuard<'_, MAX_PDI> { + pub fn outputs_raw(&self) -> PdiReadGuard<'_, MAX_PDI, R> { PdiReadGuard { lock: self.state.pdi.read(), range: self.state.config.io.output.bytes.clone(), @@ -203,7 +204,7 @@ impl SubDeviceRef<'_, SubDevicePdi<'_, MAX_PDI>> { /// Get a mutable reference to the raw output data for this SubDevice in the Process Data Image /// (PDI). - pub fn outputs_raw_mut(&self) -> PdiWriteGuard<'_, MAX_PDI> { + pub fn outputs_raw_mut(&self) -> PdiWriteGuard<'_, MAX_PDI, R> { PdiWriteGuard { lock: self.state.pdi.write(), range: self.state.config.io.output.bytes.clone(), @@ -238,7 +239,8 @@ mod tests { const LEN: usize = 64; - let pdi_storage = spin::rwlock::RwLock::new(MySyncUnsafeCell::new([0xabu8; LEN])); + let pdi_storage = + RwLock::::new(MySyncUnsafeCell::new([0xabu8; LEN])); let pdi = SubDevicePdi::new(&sd, &pdi_storage); diff --git a/src/subdevice_group/group_id.rs b/src/subdevice_group/group_id.rs index 37547381..267f1c30 100644 --- a/src/subdevice_group/group_id.rs +++ b/src/subdevice_group/group_id.rs @@ -15,9 +15,9 @@ mod tests { #[test] fn group_unique_id_defaults() { - let g1 = SubDeviceGroup::<16, 16, PreOp>::default(); - let g2 = SubDeviceGroup::<16, 16, PreOp>::default(); - let g3 = SubDeviceGroup::<16, 16, PreOp>::default(); + let g1 = SubDeviceGroup::<16, 16, crate::DefaultLock, PreOp>::default(); + let g2 = SubDeviceGroup::<16, 16, crate::DefaultLock, PreOp>::default(); + let g3 = SubDeviceGroup::<16, 16, crate::DefaultLock, PreOp>::default(); assert_ne!(g1.id, g2.id); assert_ne!(g2.id, g3.id); diff --git a/src/subdevice_group/handle.rs b/src/subdevice_group/handle.rs index dd99497a..a24981af 100644 --- a/src/subdevice_group/handle.rs +++ b/src/subdevice_group/handle.rs @@ -1,6 +1,7 @@ use crate::{ GroupId, MainDevice, SubDevice, SubDeviceGroup, SubDeviceRef, error::Error, fmt, pdi::PdiOffset, }; +use lock_api::RawRwLock; /// A trait implemented only by [`SubDeviceGroup`] so multiple groups with different const params /// can be stored in a hashmap, `Vec`, etc. @@ -18,8 +19,8 @@ pub trait SubDeviceGroupHandle: Sync { } #[sealed::sealed] -impl SubDeviceGroupHandle - for SubDeviceGroup +impl SubDeviceGroupHandle + for SubDeviceGroup where S: Sync, { diff --git a/src/subdevice_group/mod.rs b/src/subdevice_group/mod.rs index 3c6dc5e2..79b0b082 100644 --- a/src/subdevice_group/mod.rs +++ b/src/subdevice_group/mod.rs @@ -26,6 +26,7 @@ use crate::{ }; use core::{cell::UnsafeCell, marker::PhantomData, sync::atomic::AtomicUsize, time::Duration}; use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireSized}; +use lock_api::{RawRwLock, RwLock, RwLockWriteGuard}; pub use self::group_id::GroupId; pub use self::handle::SubDeviceGroupHandle; @@ -174,9 +175,15 @@ pub struct CycleInfo { /// Groups are created during EtherCrab initialisation, and are the only way to access individual /// SubDevice PDI sections. #[doc(alias = "SlaveGroup")] -pub struct SubDeviceGroup { +pub struct SubDeviceGroup< + const MAX_SUBDEVICES: usize, + const MAX_PDI: usize, + R: RawRwLock = crate::DefaultLock, + S = PreOp, + DC = NoDc, +> { id: GroupId, - pdi: spin::rwlock::RwLock, crate::SpinStrategy>, + pdi: RwLock>, /// The number of bytes at the beginning of the PDI reserved for SubDevice inputs. read_pdi_len: usize, /// The total length (I and O) of the PDI for this group. @@ -186,8 +193,8 @@ pub struct SubDeviceGroup, } -impl - SubDeviceGroup +impl + SubDeviceGroup { /// Configure read/write FMMUs and PDI for this group. async fn configure_fmmus(&mut self, maindevice: &MainDevice<'_>) -> Result<(), Error> { @@ -283,7 +290,7 @@ impl pub async fn into_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { let self_ = self.into_safe_op(maindevice).await?; self_.into_op(maindevice).await @@ -297,7 +304,7 @@ impl pub async fn into_pre_op_pdi( mut self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.configure_fmmus(maindevice).await?; Ok(SubDeviceGroup { @@ -315,7 +322,7 @@ impl pub async fn into_safe_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { let self_ = self.into_pre_op_pdi(maindevice).await?; // We're done configuring FMMUs, etc, now we can request all SubDevices in this group go into @@ -329,7 +336,7 @@ impl pub async fn into_init( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::Init).await } @@ -357,8 +364,8 @@ impl } } -impl - SubDeviceGroup +impl + SubDeviceGroup where S: IsPreOp, { @@ -379,7 +386,7 @@ where self, maindevice: &MainDevice<'_>, dc_conf: DcConfiguration, - ) -> Result, Error> { + ) -> Result, Error> { fmt::debug!("Configuring distributed clocks for group"); let Some(reference) = maindevice.dc_ref_address() else { @@ -491,14 +498,14 @@ where } } -impl - SubDeviceGroup +impl + SubDeviceGroup { /// Transition the SubDevice group from PRE-OP to SAFE-OP. pub async fn into_safe_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::SafeOp).await } @@ -509,7 +516,7 @@ impl pub async fn into_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { let self_ = self.into_safe_op(maindevice).await?; self_.transition_to(maindevice, SubDeviceState::Op).await @@ -526,7 +533,7 @@ impl pub async fn request_into_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { let self_ = self.into_safe_op(maindevice).await?; self_.request_into_op(maindevice).await @@ -536,19 +543,19 @@ impl pub async fn into_init( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::Init).await } } -impl - SubDeviceGroup +impl + SubDeviceGroup { /// Transition all SubDevices in the group from SAFE-OP to OP. pub async fn into_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::Op).await } @@ -556,7 +563,7 @@ impl pub async fn into_pre_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::PreOp).await } @@ -571,7 +578,7 @@ impl pub async fn request_into_op( mut self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { for subdevice in self.inner.get_mut().subdevices.iter_mut() { SubDeviceRef::new(maindevice, subdevice.configured_address(), subdevice) .request_subdevice_state_nowait(SubDeviceState::Op) @@ -590,25 +597,25 @@ impl } } -impl - SubDeviceGroup +impl + SubDeviceGroup { /// Transition all SubDevices in the group from OP to SAFE-OP. pub async fn into_safe_op( self, maindevice: &MainDevice<'_>, - ) -> Result, Error> { + ) -> Result, Error> { self.transition_to(maindevice, SubDeviceState::SafeOp).await } } -impl Default - for SubDeviceGroup +impl Default + for SubDeviceGroup { fn default() -> Self { Self { id: GroupId(GROUP_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed)), - pdi: spin::rwlock::RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), + pdi: RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), read_pdi_len: Default::default(), pdi_len: Default::default(), inner: MySyncUnsafeCell::new(GroupInner::default()), @@ -618,8 +625,8 @@ impl Default } } -impl - SubDeviceGroup +impl + SubDeviceGroup { fn inner(&self) -> &GroupInner { unsafe { &*self.inner.get() } @@ -716,7 +723,7 @@ impl mut self, maindevice: &MainDevice<'_>, desired_state: SubDeviceState, - ) -> Result, Error> { + ) -> Result, Error> { // We're done configuring FMMUs, etc, now we can request all SubDevices in this group go into // SAFE-OP for subdevice in self.inner.get_mut().subdevices.iter_mut() { @@ -786,8 +793,8 @@ where } // Methods for any state where a PDI has been configured. -impl - SubDeviceGroup +impl + SubDeviceGroup where S: HasPdi, { @@ -797,7 +804,7 @@ where &'group self, maindevice: &'maindevice MainDevice<'maindevice>, index: usize, - ) -> Result>, Error> { + ) -> Result>, Error> { let subdevice = self.inner().subdevices.get(index).ok_or(Error::NotFound { item: Item::SubDevice, index: Some(index), @@ -830,7 +837,7 @@ where pub fn iter<'group, 'maindevice>( &'group self, maindevice: &'maindevice MainDevice<'maindevice>, - ) -> impl Iterator>> + ) -> impl Iterator>> where 'maindevice: 'group, { @@ -1099,11 +1106,7 @@ where total_bytes_sent: usize, bytes_in_this_chunk: usize, data: &ReceivedPdu<'_>, - pdi_lock: &mut spin::rwlock::RwLockWriteGuard< - '_, - MySyncUnsafeCell<[u8; MAX_PDI]>, - crate::SpinStrategy, - >, + pdi_lock: &mut RwLockWriteGuard<'_, R, MySyncUnsafeCell<[u8; MAX_PDI]>>, ) -> Result { let wkc = data.working_counter; @@ -1119,8 +1122,8 @@ where } // Methods for when the group has a PDI AND has Distributed Clocks configured -impl - SubDeviceGroup +impl + SubDeviceGroup where S: HasPdi, { @@ -1454,18 +1457,19 @@ mod tests { MainDeviceConfig::default(), )); - let group: SubDeviceGroup = SubDeviceGroup { - id: GroupId(0), - pdi: spin::rwlock::RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), - read_pdi_len: 32, - pdi_len: 96, - inner: MySyncUnsafeCell::new(GroupInner { - subdevices: heapless::Vec::new(), - pdi_start: PdiOffset::default(), - }), - dc_conf: NoDc, - _state: PhantomData, - }; + let group: SubDeviceGroup = + SubDeviceGroup { + id: GroupId(0), + pdi: RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), + read_pdi_len: 32, + pdi_len: 96, + inner: MySyncUnsafeCell::new(GroupInner { + subdevices: heapless::Vec::new(), + pdi_start: PdiOffset::default(), + }), + dc_conf: NoDc, + _state: PhantomData, + }; let out = group.tx_rx(&maindevice).await; @@ -1683,9 +1687,9 @@ mod tests { // Test setup had 16 devices assert_eq!(subdevices.len(), 16); - let group = SubDeviceGroup { + let group = SubDeviceGroup:: { id: GroupId(0), - pdi: spin::rwlock::RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), + pdi: RwLock::new(MySyncUnsafeCell::new([0u8; MAX_PDI])), read_pdi_len: 406, pdi_len: 474, inner: MySyncUnsafeCell::new(GroupInner { diff --git a/tests/replay-ek1100-el2828-el2889.rs b/tests/replay-ek1100-el2828-el2889.rs index 3110b1f5..8f5356e3 100644 --- a/tests/replay-ek1100-el2828-el2889.rs +++ b/tests/replay-ek1100-el2828-el2889.rs @@ -9,10 +9,7 @@ mod util; use env_logger::Env; -use ethercrab::{ - MainDevice, MainDeviceConfig, PduStorage, SubDeviceGroup, Timeouts, error::Error, - subdevice_group, -}; +use ethercrab::{MainDevice, MainDeviceConfig, PduStorage, SubDeviceGroup, Timeouts, error::Error}; use std::{path::PathBuf, time::Duration}; use tokio::time::MissedTickBehavior; @@ -25,9 +22,9 @@ struct Groups { /// EL2889 and EK1100. 2 items, 2 bytes of PDI for 16 output bits. /// /// We'll keep the EK1100 in here as it has no PDI but still needs to live somewhere. - slow_outputs: SubDeviceGroup<2, 2, subdevice_group::PreOp>, + slow_outputs: SubDeviceGroup<2, 2>, /// EL2828. 1 item, 1 byte of PDI for 8 output bits. - fast_outputs: SubDeviceGroup<1, 1, subdevice_group::PreOp>, + fast_outputs: SubDeviceGroup<1, 1>, } #[tokio::test]