Skip to content

Commit

Permalink
Adding SPI atomic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ryan-summers committed Mar 11, 2024
1 parent a3d01e1 commit 735b635
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
129 changes: 129 additions & 0 deletions embedded-hal-bus/src/spi/atomic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use core::cell::UnsafeCell;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{Error, ErrorKind, ErrorType, Operation, SpiBus, SpiDevice};

use super::DeviceError;
use crate::spi::shared::transaction;

/// `UnsafeCell`-based shared bus [`SpiDevice`] implementation.
///
/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances,
/// each with its own `CS` pin.
///
/// Sharing is implemented with a `UnsafeCell`. This means it has low overhead, and, unlike [`crate::spi::RefCellDevice`], instances are `Send`,
/// so it only allows sharing across multiple threads (interrupt priority level). When attempting
/// to preempt usage of the bus, a `AtomicError::Busy` error is returned.
///
/// This primitive is particularly well-suited for applications that have external arbitration
/// rules, such as the RTIC framework.
///
pub struct AtomicDevice<'a, BUS, CS, D> {
bus: &'a UnsafeCell<BUS>,
cs: CS,
delay: D,
busy: portable_atomic::AtomicBool,
}

#[derive(Debug, Copy, Clone)]
/// Wrapper type for errors originating from the atomically-checked I2C bus manager.
pub enum AtomicError<T: Error> {
/// This error is returned if the I2C bus was already in use when an operation was attempted,
/// which indicates that the driver requirements are not being met with regard to
/// synchronization.
Busy,

/// An I2C-related error occurred, and the internal error should be inspected.
Other(T),
}

impl<'a, BUS, CS, D> AtomicDevice<'a, BUS, CS, D> {
/// Create a new [`AtomicDevice`].
#[inline]
pub fn new(bus: &'a UnsafeCell<BUS>, cs: CS, delay: D) -> Self {
Self {
bus,
cs,
delay,
busy: portable_atomic::AtomicBool::from(false),
}
}
}

impl<'a, BUS, CS> AtomicDevice<'a, BUS, CS, super::NoDelay>
where
BUS: ErrorType,
CS: OutputPin,
{
/// Create a new [`AtomicDevice`] without support for in-transaction delays.
///
/// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice`
/// contract, which mandates delay support. It is relatively rare for drivers to use
/// in-transaction delays, so you might still want to use this method because it's more practical.
///
/// Note that a future version of the driver might start using delays, causing your
/// code to panic. This wouldn't be considered a breaking change from the driver side, because
/// drivers are allowed to assume `SpiDevice` implementations comply with the contract.
/// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
/// the driver crate, you might want to pin the driver's version.
///
/// # Panics
///
/// The returned device will panic if you try to execute a transaction
/// that contains any operations of type [`Operation::DelayNs`].
#[inline]
pub fn new_no_delay(bus: &'a UnsafeCell<BUS>, cs: CS) -> Self {
Self {
bus,
cs,
delay: super::NoDelay,
busy: portable_atomic::AtomicBool::from(false),
}
}
}

unsafe impl<'a, BUS, CS, D> Send for AtomicDevice<'a, BUS, CS, D> {}

impl<T: Error> Error for AtomicError<T> {
fn kind(&self) -> ErrorKind {
match self {
AtomicError::Other(e) => e.kind(),
_ => ErrorKind::Other,
}
}
}

impl<'a, BUS, CS, D> ErrorType for AtomicDevice<'a, BUS, CS, D>
where
BUS: ErrorType,
CS: OutputPin,
{
type Error = AtomicError<DeviceError<BUS::Error, CS::Error>>;
}

impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for AtomicDevice<'a, BUS, CS, D>
where
BUS: SpiBus<Word>,
CS: OutputPin,
D: DelayNs,
{
#[inline]
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
self.busy
.compare_exchange(
false,
true,
core::sync::atomic::Ordering::SeqCst,
core::sync::atomic::Ordering::SeqCst,
)
.map_err(|_| AtomicError::Busy)?;

let bus = unsafe { &mut *self.bus.get() };

let result = transaction(operations, bus, &mut self.delay, &mut self.cs);

self.busy.store(false, core::sync::atomic::Ordering::SeqCst);

result.map_err(AtomicError::Other)
}
}
2 changes: 2 additions & 0 deletions embedded-hal-bus/src/spi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ pub use refcell::*;
mod mutex;
#[cfg(feature = "std")]
pub use mutex::*;
mod atomic;
mod critical_section;
mod shared;
pub use atomic::*;

pub use self::critical_section::*;

Expand Down

0 comments on commit 735b635

Please sign in to comment.