|
322 | 322 | //! `SpiFuture` implements `AsRef<Spi>` and `AsMut<Spi>` so
|
323 | 323 | //! that it can be reconfigured using the regular [`Spi`] methods.
|
324 | 324 | //!
|
| 325 | +//! ## Considerations when using `async` [`Spi`] with DMA <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span> <span class="stab portability" title="Available on crate feature `dma` only"><code>dma</code></span> |
| 326 | +//! |
| 327 | +//! * An [`Spi`] struct must be turned into an [`SpiFuture`] by calling |
| 328 | +//! [`Spi::into_future`] before calling `with_dma_channel`. The DMA channel |
| 329 | +//! itself must also be configured in async mode by using |
| 330 | +//! [`DmaController::into_future`](crate::dmac::DmaController::into_future). |
| 331 | +//! If a DMA channel is added to the [`Spi`] struct before it is turned into |
| 332 | +//! an [`SpiFuture`], it will not be able to use DMA in async mode. |
| 333 | +//! |
| 334 | +//! ``` |
| 335 | +//! // This will work |
| 336 | +//! let spi = spi.into_future().with_dma_channels(rx_channel, tx_channel); |
| 337 | +//! |
| 338 | +//! // This won't |
| 339 | +//! let spi = spi.with_dma_channels(rx_channel, tx_channel).into_future(); |
| 340 | +//! ``` |
| 341 | +//! |
| 342 | +//! ### Safety considerations |
| 343 | +//! |
| 344 | +//! In `async` mode, an SPI+DMA transfer does not require `'static` source and |
| 345 | +//! destination buffers. This, in theory, makes its use `unsafe`. However it is |
| 346 | +//! marked as safe for better ergonomics, and to enable the implementation of |
| 347 | +//! the [`embedded_hal_async::spi::SpiBus`] trait. |
| 348 | +//! |
| 349 | +//! This means that, as an user, you **must** ensure that the [`Future`]s |
| 350 | +//! returned by the [`embedded_hal_async::spi::SpiBus`] methods may never be |
| 351 | +//! forgotten through [`forget`] or by wrapping them with a [`ManuallyDrop`]. |
| 352 | +//! |
| 353 | +//! The returned futures implement [`Drop`] and will automatically stop any |
| 354 | +//! ongoing transfers; this guarantees that the memory occupied by the |
| 355 | +//! now-dropped buffers may not be corrupted by running transfers. |
| 356 | +//! |
| 357 | +//! This means that using functions like [`futures::select_biased`] to implement |
| 358 | +//! timeouts is safe; transfers will be safely cancelled if the timeout expires. |
| 359 | +//! |
| 360 | +//! This also means that should you [`forget`] this [`Future`] after its |
| 361 | +//! first [`poll`] call, the transfer will keep running, ruining the |
| 362 | +//! now-reclaimed memory, as well as the rest of your day. |
| 363 | +//! |
| 364 | +//! * `await`ing is fine: the [`Future`] will run to completion. |
| 365 | +//! * Dropping an incomplete transfer is also fine. Dropping can happen, for |
| 366 | +//! example, if the transfer doesn't complete before a timeout expires. |
| 367 | +//! * Dropping an incomplete transfer *without running its destructor* is |
| 368 | +//! **unsound** and will trigger undefined behavior. |
| 369 | +//! |
| 370 | +//! ```ignore |
| 371 | +//! async fn always_ready() {} |
| 372 | +//! |
| 373 | +//! let mut buffer = [0x00; 10]; |
| 374 | +//! |
| 375 | +//! // This is completely safe |
| 376 | +//! spi.read(&mut buffer).await?; |
| 377 | +//! |
| 378 | +//! // This is also safe: we launch a transfer, which is then immediately cancelled |
| 379 | +//! futures::select_biased! { |
| 380 | +//! _ = spi.read(&mut buffer)?, |
| 381 | +//! _ = always_ready(), |
| 382 | +//! } |
| 383 | +//! |
| 384 | +//! // This, while contrived, is also safe. |
| 385 | +//! { |
| 386 | +//! use core::future::Future; |
| 387 | +//! |
| 388 | +//! let future = spi.read(&mut buffer); |
| 389 | +//! futures::pin_mut!(future); |
| 390 | +//! // Assume ctx is a `core::task::Context` given out by the executor. |
| 391 | +//! // The future is polled, therefore starting the transfer |
| 392 | +//! future.as_mut().poll(ctx); |
| 393 | +//! |
| 394 | +//! // Future is dropped here - transfer is cancelled. |
| 395 | +//! } |
| 396 | +//! |
| 397 | +//! // DANGER: This is an example of undefined behavior |
| 398 | +//! { |
| 399 | +//! use core::future::Future; |
| 400 | +//! use core::ops::DerefMut; |
| 401 | +//! |
| 402 | +//! let future = core::mem::ManuallyDrop::new(spi.read(&mut buffer)); |
| 403 | +//! futures::pin_mut!(future); |
| 404 | +//! // To actually make this example compile, we would need to wrap the returned |
| 405 | +//! // future from `i2c.read()` in a newtype that implements Future, because we |
| 406 | +//! // can't actually call as_mut() without being able to name the type we want |
| 407 | +//! // to deref to. |
| 408 | +//! let future_ref: &mut SomeNewTypeFuture = &mut future.as_mut(); |
| 409 | +//! future.as_mut().poll(ctx); |
| 410 | +//! |
| 411 | +//! // Future is NOT dropped here - transfer is not cancelled, resulting un UB. |
| 412 | +//! } |
| 413 | +//! ``` |
| 414 | +//! |
| 415 | +//! As you can see, unsoundness is relatively hard to come by - however, caution |
| 416 | +//! should still be exercised. |
| 417 | +//! |
325 | 418 | //! [`enable`]: Config::enable
|
326 | 419 | //! [`gpio`]: crate::gpio
|
327 | 420 | //! [`Pin`]: crate::gpio::pin::Pin
|
|
330 | 423 | //! [`embedded_hal::spi::SpiBus`]: crate::ehal::spi::SpiBus
|
331 | 424 | //! [`embedded_hal::spi::SpiDevice`]: crate::ehal::spi::SpiDevice
|
332 | 425 | //! [`async_hal`]: crate::async_hal
|
| 426 | +//! [`forget`]: core::mem::forget |
| 427 | +//! [`ManuallyDrop`]: core::mem::ManuallyDrop |
| 428 | +//! [`Future`]: core::future::Future |
| 429 | +//! [`poll`]: core::future::Future::poll |
333 | 430 |
|
334 | 431 | use core::marker::PhantomData;
|
335 | 432 |
|
@@ -1430,7 +1527,8 @@ where
|
1430 | 1527 | TxDma: crate::dmac::AnyChannel<Status = S>,
|
1431 | 1528 | S: crate::dmac::ReadyChannel,
|
1432 | 1529 | {
|
1433 |
| - /// Reclaim the DMA channels. Any subsequent SPI transaction will not use DMA. |
| 1530 | + /// Reclaim the DMA channels. Any subsequent SPI transaction will not use |
| 1531 | + /// DMA. |
1434 | 1532 | pub fn take_dma_channels(self) -> (Spi<C, D, NoneT, NoneT>, RxDma, TxDma) {
|
1435 | 1533 | (
|
1436 | 1534 | Spi {
|
@@ -1500,7 +1598,8 @@ where
|
1500 | 1598 | R: crate::dmac::AnyChannel<Status = S>,
|
1501 | 1599 | S: crate::dmac::ReadyChannel,
|
1502 | 1600 | {
|
1503 |
| - /// Reclaim the Rx DMA channel. Any subsequent SPI transaction will not use DMA. |
| 1601 | + /// Reclaim the Rx DMA channel. Any subsequent SPI transaction will not use |
| 1602 | + /// DMA. |
1504 | 1603 | #[cfg(feature = "dma")]
|
1505 | 1604 | pub fn take_rx_channel(self) -> (Spi<C, D, NoneT, T>, R) {
|
1506 | 1605 | (
|
@@ -1546,7 +1645,8 @@ where
|
1546 | 1645 | T: crate::dmac::AnyChannel<Status = S>,
|
1547 | 1646 | S: crate::dmac::ReadyChannel,
|
1548 | 1647 | {
|
1549 |
| - /// Reclaim the DMA channel. Any subsequent SPI transaction will not use DMA. |
| 1648 | + /// Reclaim the DMA channel. Any subsequent SPI transaction will not use |
| 1649 | + /// DMA. |
1550 | 1650 | pub fn take_tx_channel(self) -> (Spi<C, D, R, NoneT>, T) {
|
1551 | 1651 | (
|
1552 | 1652 | Spi {
|
|
0 commit comments