Skip to content

Commit 8790887

Browse files
Merge #404
404: async/spi: Add a transaction helper macro r=Dirbaio a=GrantM11235 Based on #403 This macro allows users to use `SpiDevice::transaction` in a completely safe way, without needing to dereference a raw pointer. The macro is exported as `embedded_hal_async::spi_transaction_helper` because exported macros always go in the root of the crate. It is also publicly reexported as `embedded_hal_async::spi::transaction_helper` because I think that most people would expect to find it in the spi module. It is not possible to apply `doc(hidden)` to the instance in the root of the crate because that would also hide the reexport, see rust-lang/rust#59368. Co-authored-by: Grant Miller <[email protected]>
2 parents 1f87498 + edcb4fc commit 8790887

File tree

3 files changed

+119
-21
lines changed

3 files changed

+119
-21
lines changed

embedded-hal-async/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
## [v0.1.0-alpha.1] - 2022-05-24
1111

12+
### Added
13+
14+
- spi: added a transaction helper macro as a workaround for the raw pointer workaround.
15+
1216
### Changed
1317

1418
- spi: device helper methods (`read`, `write`, `transfer`...) are now default methods in `SpiDevice` instead of an `SpiDeviceExt` extension trait.

embedded-hal-async/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
1010
#![warn(missing_docs)]
1111
#![no_std]
12-
#![feature(generic_associated_types)]
1312
#![feature(type_alias_impl_trait)]
1413

1514
pub mod delay;

embedded-hal-async/src/spi.rs

+115-20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,107 @@ where
3535
Word: Copy + 'static,
3636
= impl Future<Output = Result<(), T::Error>>;
3737

38+
#[macro_export]
39+
/// Do an SPI transaction on a bus.
40+
/// This is a safe wrapper for [SpiDevice::transaction], which handles dereferencing the raw pointer for you.
41+
///
42+
/// # Examples
43+
///
44+
/// ```
45+
/// use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice};
46+
///
47+
/// pub async fn transaction_example<SPI>(mut device: SPI) -> Result<u32, SPI::Error>
48+
/// where
49+
/// SPI: SpiDevice,
50+
/// SPI::Bus: SpiBus,
51+
/// {
52+
/// transaction!(&mut device, move |bus| async move {
53+
/// // Unlike `SpiDevice::transaction`, we don't need to
54+
/// // manually dereference a pointer in order to use the bus.
55+
/// bus.write(&[42]).await?;
56+
/// let mut data = [0; 4];
57+
/// bus.read(&mut data).await?;
58+
/// Ok(u32::from_be_bytes(data))
59+
/// })
60+
/// .await
61+
/// }
62+
/// ```
63+
///
64+
/// Note that the compiler will prevent you from moving the bus reference outside of the closure
65+
/// ```compile_fail
66+
/// # use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice};
67+
/// #
68+
/// # pub async fn smuggle_test<SPI>(mut device: SPI) -> Result<(), SPI::Error>
69+
/// # where
70+
/// # SPI: SpiDevice,
71+
/// # SPI::Bus: SpiBus,
72+
/// # {
73+
/// let mut bus_smuggler: Option<&mut SPI::Bus> = None;
74+
/// transaction!(&mut device, move |bus| async move {
75+
/// bus_smuggler = Some(bus);
76+
/// Ok(())
77+
/// })
78+
/// .await
79+
/// # }
80+
/// ```
81+
macro_rules! spi_transaction {
82+
($device:expr, move |$bus:ident| async move $closure_body:expr) => {
83+
$crate::spi::SpiDevice::transaction($device, move |$bus| {
84+
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is
85+
// valid and dereferencable for the entire duration of the closure.
86+
let $bus = unsafe { &mut *$bus };
87+
async move {
88+
let result = $closure_body;
89+
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure
90+
let _ = $bus; // Silence the "unused variable" warning from prevous line
91+
result
92+
}
93+
})
94+
};
95+
($device:expr, move |$bus:ident| async $closure_body:expr) => {
96+
$crate::spi::SpiDevice::transaction($device, move |$bus| {
97+
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is
98+
// valid and dereferencable for the entire duration of the closure.
99+
let $bus = unsafe { &mut *$bus };
100+
async {
101+
let result = $closure_body;
102+
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure
103+
let _ = $bus; // Silence the "unused variable" warning from prevous line
104+
result
105+
}
106+
})
107+
};
108+
($device:expr, |$bus:ident| async move $closure_body:expr) => {
109+
$crate::spi::SpiDevice::transaction($device, |$bus| {
110+
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is
111+
// valid and dereferencable for the entire duration of the closure.
112+
let $bus = unsafe { &mut *$bus };
113+
async move {
114+
let result = $closure_body;
115+
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure
116+
let _ = $bus; // Silence the "unused variable" warning from prevous line
117+
result
118+
}
119+
})
120+
};
121+
($device:expr, |$bus:ident| async $closure_body:expr) => {
122+
$crate::spi::SpiDevice::transaction($device, |$bus| {
123+
// Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is
124+
// valid and dereferencable for the entire duration of the closure.
125+
let $bus = unsafe { &mut *$bus };
126+
async {
127+
let result = $closure_body;
128+
let $bus = $bus; // Ensure that the bus reference was not moved out of the closure
129+
let _ = $bus; // Silence the "unused variable" warning from prevous line
130+
result
131+
}
132+
})
133+
};
134+
}
135+
136+
#[doc(inline)]
137+
pub use spi_transaction as transaction;
138+
38139
/// SPI device trait
39140
///
40141
/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected
@@ -59,6 +160,10 @@ pub unsafe trait SpiDevice: ErrorType {
59160

60161
/// Perform a transaction against the device.
61162
///
163+
/// **NOTE:**
164+
/// It is not recommended to use this method directly, because it requires `unsafe` code to dereference the raw pointer.
165+
/// Instead, the [`transaction!`] macro should be used, which handles this safely inside the macro.
166+
///
62167
/// - Locks the bus
63168
/// - Asserts the CS (Chip Select) pin.
64169
/// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device.
@@ -95,11 +200,7 @@ pub unsafe trait SpiDevice: ErrorType {
95200
Self::Bus: SpiBusRead<Word>,
96201
Word: Copy + 'static,
97202
{
98-
self.transaction(move |bus| async move {
99-
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
100-
let bus = unsafe { &mut *bus };
101-
bus.read(buf).await
102-
})
203+
transaction!(self, move |bus| async move { bus.read(buf).await })
103204
}
104205

105206
/// Do a write within a transaction.
@@ -112,11 +213,7 @@ pub unsafe trait SpiDevice: ErrorType {
112213
Self::Bus: SpiBusWrite<Word>,
113214
Word: Copy + 'static,
114215
{
115-
self.transaction(move |bus| async move {
116-
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
117-
let bus = unsafe { &mut *bus };
118-
bus.write(buf).await
119-
})
216+
transaction!(self, move |bus| async move { bus.write(buf).await })
120217
}
121218

122219
/// Do a transfer within a transaction.
@@ -133,11 +230,10 @@ pub unsafe trait SpiDevice: ErrorType {
133230
Self::Bus: SpiBus<Word>,
134231
Word: Copy + 'static,
135232
{
136-
self.transaction(move |bus| async move {
137-
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
138-
let bus = unsafe { &mut *bus };
139-
bus.transfer(read, write).await
140-
})
233+
transaction!(
234+
self,
235+
move |bus| async move { bus.transfer(read, write).await }
236+
)
141237
}
142238

143239
/// Do an in-place transfer within a transaction.
@@ -153,11 +249,10 @@ pub unsafe trait SpiDevice: ErrorType {
153249
Self::Bus: SpiBus<Word>,
154250
Word: Copy + 'static,
155251
{
156-
self.transaction(move |bus| async move {
157-
// safety: `bus` is a valid pointer we're allowed to use for the duration of the closure.
158-
let bus = unsafe { &mut *bus };
159-
bus.transfer_in_place(buf).await
160-
})
252+
transaction!(
253+
self,
254+
move |bus| async move { bus.transfer_in_place(buf).await }
255+
)
161256
}
162257
}
163258

0 commit comments

Comments
 (0)