Skip to content

Commit

Permalink
Merge #36
Browse files Browse the repository at this point in the history
36: Memory safe DMA API r=adamgreig a=japaric

This is a first draft on building memory safe DMA abstractions.

r? @rust-embedded/resources
cc @korken89 @jamesmunns


Co-authored-by: Jorge Aparicio <[email protected]>
Co-authored-by: Emil Fresk <[email protected]>
  • Loading branch information
3 people committed Jan 22, 2019
2 parents 2b523c5 + 828228b commit a8826d3
Show file tree
Hide file tree
Showing 14 changed files with 1,473 additions and 0 deletions.
3 changes: 3 additions & 0 deletions ci/dma/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
.#*
**/*.rs.bk
9 changes: 9 additions & 0 deletions ci/dma/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
authors = ["Jorge Aparicio <[email protected]>"]
edition = "2018"
name = "shared"
version = "0.1.0"

[dependencies]
as-slice = "0.1.0"
pin-utils = "0.1.0-alpha.4"
151 changes: 151 additions & 0 deletions ci/dma/examples/eight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//! Destructors
#![deny(missing_docs, warnings)]

use core::{
hint,
marker::Unpin,
mem,
ops::{Deref, DerefMut},
pin::Pin,
ptr,
sync::atomic::{self, Ordering},
};

use as_slice::{AsMutSlice, AsSlice};
use shared::{Dma1Channel1, USART1_RX, USART1_TX};

/// A DMA transfer
pub struct Transfer<B> {
// NOTE: always `Some` variant
inner: Option<Inner<B>>,
}

// NOTE: previously named `Transfer<B>`
struct Inner<B> {
buffer: Pin<B>,
serial: Serial1,
}

impl<B> Transfer<B> {
/// Blocks until the transfer is done and returns the buffer
pub fn wait(mut self) -> (Pin<B>, Serial1) {
while !self.is_done() {}

atomic::compiler_fence(Ordering::Acquire);

let inner = self
.inner
.take()
.unwrap_or_else(|| unsafe { hint::unreachable_unchecked() });
(inner.buffer, inner.serial)
}
}

impl<B> Drop for Transfer<B> {
fn drop(&mut self) {
if let Some(inner) = self.inner.as_mut() {
// NOTE: this is a volatile write
inner.serial.dma.stop();

// we need a read here to make the Acquire fence effective
// we do *not* need this if `dma.stop` does a RMW operation
unsafe {
ptr::read_volatile(&0);
}

// we need a fence here for the same reason we need one in `Transfer.wait`
atomic::compiler_fence(Ordering::Acquire);
}
}
}

impl Serial1 {
/// Receives data into the given `buffer` until it's filled
///
/// Returns a value that represents the in-progress DMA transfer
pub fn read_exact<B>(mut self, mut buffer: Pin<B>) -> Transfer<B>
where
B: DerefMut + 'static,
B::Target: AsMutSlice<Element = u8> + Unpin,
{
// .. same as before ..
let slice = buffer.as_mut_slice();
let (ptr, len) = (slice.as_mut_ptr(), slice.len());

self.dma.set_source_address(USART1_RX, false);
self.dma.set_destination_address(ptr as usize, true);
self.dma.set_transfer_length(len);

atomic::compiler_fence(Ordering::Release);
self.dma.start();

Transfer {
inner: Some(Inner {
buffer,
serial: self,
}),
}
}

/// Sends out the given `buffer`
///
/// Returns a value that represents the in-progress DMA transfer
pub fn write_all<B>(mut self, buffer: Pin<B>) -> Transfer<B>
where
B: Deref + 'static,
B::Target: AsSlice<Element = u8>,
{
// .. same as before ..
let slice = buffer.as_slice();
let (ptr, len) = (slice.as_ptr(), slice.len());

self.dma.set_destination_address(USART1_TX, false);
self.dma.set_source_address(ptr as usize, true);
self.dma.set_transfer_length(len);

atomic::compiler_fence(Ordering::Release);
self.dma.start();

Transfer {
inner: Some(Inner {
buffer,
serial: self,
}),
}
}
}

#[allow(dead_code, unused_mut, unused_variables)]
fn reuse(serial: Serial1) {
let buf = Pin::new(Box::new([0; 16]));

let t = serial.read_exact(buf); // compiler_fence(Ordering::Release) ▲

// ..

// this stops the DMA transfer and frees memory
mem::drop(t); // compiler_fence(Ordering::Acquire) ▼

// this likely reuses the previous memory allocation
let mut buf = Box::new([0; 16]);

// .. do stuff with `buf` ..
}

// UNCHANGED

fn main() {}

/// A singleton that represents serial port #1
pub struct Serial1 {
dma: Dma1Channel1,
// ..
}

impl<B> Transfer<B> {
/// Returns `true` if the DMA transfer has finished
pub fn is_done(&self) -> bool {
!Dma1Channel1::in_progress()
}
}
144 changes: 144 additions & 0 deletions ci/dma/examples/five.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! Generic buffer
#![deny(missing_docs, warnings)]

use core::sync::atomic::{self, Ordering};

use shared::{Dma1Channel1, USART1_RX, USART1_TX};

// as-slice = "0.1.0"
use as_slice::{AsMutSlice, AsSlice};

impl Serial1 {
/// Receives data into the given `buffer` until it's filled
///
/// Returns a value that represents the in-progress DMA transfer
pub fn read_exact<B>(mut self, mut buffer: B) -> Transfer<B>
where
B: AsMutSlice<Element = u8>,
{
// NOTE: added
let slice = buffer.as_mut_slice();
let (ptr, len) = (slice.as_mut_ptr(), slice.len());

self.dma.set_source_address(USART1_RX, false);

// NOTE: tweaked
self.dma.set_destination_address(ptr as usize, true);
self.dma.set_transfer_length(len);

atomic::compiler_fence(Ordering::Release);
self.dma.start();

Transfer {
buffer,
serial: self,
}
}

/// Sends out the given `buffer`
///
/// Returns a value that represents the in-progress DMA transfer
fn write_all<B>(mut self, buffer: B) -> Transfer<B>
where
B: AsSlice<Element = u8>,
{
// NOTE: added
let slice = buffer.as_slice();
let (ptr, len) = (slice.as_ptr(), slice.len());

self.dma.set_destination_address(USART1_TX, false);

// NOTE: tweaked
self.dma.set_source_address(ptr as usize, true);
self.dma.set_transfer_length(len);

atomic::compiler_fence(Ordering::Release);
self.dma.start();

Transfer {
buffer,
serial: self,
}
}
}

#[allow(dead_code, unused_variables)]
fn reuse(serial: Serial1, msg: &'static mut [u8]) {
// send a message
let t1 = serial.write_all(msg);

// ..

let (msg, serial) = t1.wait(); // `msg` is now `&'static [u8]`

msg.reverse();

// now send it in reverse
let t2 = serial.write_all(msg);

// ..

let (buf, serial) = t2.wait();

// ..
}

#[allow(dead_code, unused_variables)]
fn invalidate(serial: Serial1) {
let t = start(serial);

bar();

let (buf, serial) = t.wait();
}

#[inline(never)]
fn start(serial: Serial1) -> Transfer<[u8; 16]> {
// array allocated in this frame
let buffer = [0; 16];

serial.read_exact(buffer)
}

#[allow(unused_mut, unused_variables)]
#[inline(never)]
fn bar() {
// stack variables
let mut x = 0;
let mut y = 0;

// use `x` and `y`
}

// UNCHANGED

fn main() {}

/// A DMA transfer
pub struct Transfer<B> {
buffer: B,
serial: Serial1,
}

/// A singleton that represents serial port #1
pub struct Serial1 {
dma: Dma1Channel1,
// ..
}

impl<B> Transfer<B> {
/// Returns `true` if the DMA transfer has finished
pub fn is_done(&self) -> bool {
!Dma1Channel1::in_progress()
}

/// Blocks until the transfer is done and returns the buffer
pub fn wait(self) -> (B, Serial1) {
while !self.is_done() {}

atomic::compiler_fence(Ordering::Acquire);

(self.buffer, self.serial)
}
}
Loading

0 comments on commit a8826d3

Please sign in to comment.