-
Notifications
You must be signed in to change notification settings - Fork 235
async/spi: Add a transaction helper macro #404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bors
merged 11 commits into
rust-embedded:master
from
GrantM11235:async-transaction-helper
Sep 21, 2022
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
597493d
Make the async SpiDevice trait unsafe to implement
GrantM11235 93eb20f
async/spi: Add a transaction helper macro
GrantM11235 eeec389
Replace `drop($bus)` with `let $bus = $bus`
GrantM11235 a75b292
Dereference pointer before async block
GrantM11235 a6f623b
Add a `compile_fail` doctest
GrantM11235 0392676
Add support for all `move` combinations
GrantM11235 1ae5990
Rename `transaction_helper!` to `transaction`
GrantM11235 6c9f380
Improve `transaction!` docs
GrantM11235 5842633
Add a note about the macro in the transaction method docs
GrantM11235 c99bbdb
Remove trailing space
GrantM11235 edcb4fc
Remove GAT feature attribute
GrantM11235 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,13 +35,118 @@ where | |
Word: Copy + 'static, | ||
= impl Future<Output = Result<(), T::Error>>; | ||
|
||
#[macro_export] | ||
/// Do an SPI transaction on a bus. | ||
/// This is a safe wrapper for [SpiDevice::transaction], which handles dereferencing the raw pointer for you. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; | ||
/// | ||
/// pub async fn transaction_example<SPI>(mut device: SPI) -> Result<u32, SPI::Error> | ||
/// where | ||
/// SPI: SpiDevice, | ||
/// SPI::Bus: SpiBus, | ||
/// { | ||
/// transaction!(&mut device, move |bus| async move { | ||
/// // Unlike `SpiDevice::transaction`, we don't need to | ||
/// // manually dereference a pointer in order to use the bus. | ||
/// bus.write(&[42]).await?; | ||
/// let mut data = [0; 4]; | ||
/// bus.read(&mut data).await?; | ||
/// Ok(u32::from_be_bytes(data)) | ||
/// }) | ||
/// .await | ||
/// } | ||
/// ``` | ||
/// | ||
/// Note that the compiler will prevent you from moving the bus reference outside of the closure | ||
/// ```compile_fail | ||
/// # use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; | ||
/// # | ||
/// # pub async fn smuggle_test<SPI>(mut device: SPI) -> Result<(), SPI::Error> | ||
/// # where | ||
/// # SPI: SpiDevice, | ||
/// # SPI::Bus: SpiBus, | ||
/// # { | ||
/// let mut bus_smuggler: Option<&mut SPI::Bus> = None; | ||
/// transaction!(&mut device, move |bus| async move { | ||
/// bus_smuggler = Some(bus); | ||
/// Ok(()) | ||
/// }) | ||
/// .await | ||
/// # } | ||
/// ``` | ||
macro_rules! spi_transaction { | ||
($device:expr, move |$bus:ident| async move $closure_body:expr) => { | ||
$crate::spi::SpiDevice::transaction($device, move |$bus| { | ||
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is | ||
// valid and dereferencable for the entire duration of the closure. | ||
let $bus = unsafe { &mut *$bus }; | ||
async move { | ||
let result = $closure_body; | ||
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure | ||
let _ = $bus; // Silence the "unused variable" warning from prevous line | ||
result | ||
} | ||
}) | ||
}; | ||
($device:expr, move |$bus:ident| async $closure_body:expr) => { | ||
$crate::spi::SpiDevice::transaction($device, move |$bus| { | ||
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is | ||
// valid and dereferencable for the entire duration of the closure. | ||
let $bus = unsafe { &mut *$bus }; | ||
async { | ||
let result = $closure_body; | ||
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure | ||
let _ = $bus; // Silence the "unused variable" warning from prevous line | ||
result | ||
} | ||
}) | ||
}; | ||
($device:expr, |$bus:ident| async move $closure_body:expr) => { | ||
$crate::spi::SpiDevice::transaction($device, |$bus| { | ||
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is | ||
// valid and dereferencable for the entire duration of the closure. | ||
let $bus = unsafe { &mut *$bus }; | ||
async move { | ||
let result = $closure_body; | ||
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure | ||
let _ = $bus; // Silence the "unused variable" warning from prevous line | ||
result | ||
} | ||
}) | ||
}; | ||
($device:expr, |$bus:ident| async $closure_body:expr) => { | ||
$crate::spi::SpiDevice::transaction($device, |$bus| { | ||
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is | ||
// valid and dereferencable for the entire duration of the closure. | ||
let $bus = unsafe { &mut *$bus }; | ||
async { | ||
let result = $closure_body; | ||
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure | ||
let _ = $bus; // Silence the "unused variable" warning from prevous line | ||
result | ||
} | ||
}) | ||
}; | ||
} | ||
|
||
#[doc(inline)] | ||
pub use spi_transaction as transaction; | ||
|
||
/// SPI device trait | ||
/// | ||
/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected | ||
/// with a CS (Chip Select) pin. | ||
/// | ||
/// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. | ||
pub trait SpiDevice: ErrorType { | ||
/// | ||
/// # Safety | ||
/// | ||
/// See [`SpiDevice::transaction`] for details. | ||
pub unsafe trait SpiDevice: ErrorType { | ||
/// SPI Bus type for this device. | ||
type Bus: ErrorType; | ||
|
||
|
@@ -55,6 +160,10 @@ pub trait SpiDevice: ErrorType { | |
|
||
/// Perform a transaction against the device. | ||
/// | ||
/// **NOTE:** | ||
/// It is not recommended to use this method directly, because it requires `unsafe` code to dereference the raw pointer. | ||
/// Instead, the [`transaction!`] macro should be used, which handles this safely inside the macro. | ||
/// | ||
/// - Locks the bus | ||
/// - Asserts the CS (Chip Select) pin. | ||
/// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. | ||
|
@@ -69,9 +178,13 @@ pub trait SpiDevice: ErrorType { | |
/// On bus errors the implementation should try to deassert CS. | ||
/// If an error occurs while deasserting CS the bus error should take priority as the return value. | ||
/// | ||
/// # Safety | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention that the |
||
/// | ||
/// The current state of the Rust typechecker doesn't allow expressing the necessary lifetime constraints, so | ||
/// the `f` closure receives a lifetime-less `*mut Bus` raw pointer instead. The pointer is guaranteed | ||
/// to be valid for the entire duration the closure is running, so dereferencing it is safe. | ||
/// the `f` closure receives a lifetime-less `*mut Bus` raw pointer instead. | ||
/// | ||
/// Implementers of the `SpiDevice` trait must guarantee that the pointer is valid and dereferencable | ||
/// for the entire duration of the closure. | ||
fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> | ||
where | ||
F: FnOnce(*mut Self::Bus) -> Fut + 'a, | ||
|
@@ -87,11 +200,7 @@ pub trait SpiDevice: ErrorType { | |
Self::Bus: SpiBusRead<Word>, | ||
Word: Copy + 'static, | ||
{ | ||
self.transaction(move |bus| async move { | ||
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure. | ||
let bus = unsafe { &mut *bus }; | ||
bus.read(buf).await | ||
}) | ||
transaction!(self, move |bus| async move { bus.read(buf).await }) | ||
} | ||
|
||
/// Do a write within a transaction. | ||
|
@@ -104,11 +213,7 @@ pub trait SpiDevice: ErrorType { | |
Self::Bus: SpiBusWrite<Word>, | ||
Word: Copy + 'static, | ||
{ | ||
self.transaction(move |bus| async move { | ||
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure. | ||
let bus = unsafe { &mut *bus }; | ||
bus.write(buf).await | ||
}) | ||
transaction!(self, move |bus| async move { bus.write(buf).await }) | ||
} | ||
|
||
/// Do a transfer within a transaction. | ||
|
@@ -125,11 +230,10 @@ pub trait SpiDevice: ErrorType { | |
Self::Bus: SpiBus<Word>, | ||
Word: Copy + 'static, | ||
{ | ||
self.transaction(move |bus| async move { | ||
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure. | ||
let bus = unsafe { &mut *bus }; | ||
bus.transfer(read, write).await | ||
}) | ||
transaction!( | ||
self, | ||
move |bus| async move { bus.transfer(read, write).await } | ||
) | ||
} | ||
|
||
/// Do an in-place transfer within a transaction. | ||
|
@@ -145,15 +249,14 @@ pub trait SpiDevice: ErrorType { | |
Self::Bus: SpiBus<Word>, | ||
Word: Copy + 'static, | ||
{ | ||
self.transaction(move |bus| async move { | ||
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure. | ||
let bus = unsafe { &mut *bus }; | ||
bus.transfer_in_place(buf).await | ||
}) | ||
transaction!( | ||
self, | ||
move |bus| async move { bus.transfer_in_place(buf).await } | ||
) | ||
} | ||
} | ||
|
||
impl<T: SpiDevice> SpiDevice for &mut T { | ||
unsafe impl<T: SpiDevice> SpiDevice for &mut T { | ||
type Bus = T::Bus; | ||
|
||
type TransactionFuture<'a, R, F, Fut> = T::TransactionFuture<'a, R, F, Fut> | ||
|
@@ -377,7 +480,7 @@ where | |
} | ||
} | ||
|
||
impl<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS> | ||
unsafe impl<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS> | ||
where | ||
BUS: SpiBusFlush, | ||
CS: OutputPin, | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.