Skip to content
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

Memory safe DMA API #36

Merged
merged 9 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {}
japaric marked this conversation as resolved.
Show resolved Hide resolved

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() {}
japaric marked this conversation as resolved.
Show resolved Hide resolved

atomic::compiler_fence(Ordering::Acquire);

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