rust-embedded / embedded-dma

Apache License 2.0
35 stars 12 forks source link

Add buffer slices #20

Open andrewgazelka opened 2 years ago

andrewgazelka commented 2 years ago

Depends on #19

Add a means for taking a slice of a buffer. I think the reasoning for this is best explained by the documentation I have written.


A [BufferSlice] is a slice which wraps either a [ReadBuffer] or a [WriteBuffer]

Use Case Many HALs use the length of a {Read,Write}Buffer to configure DMA Transfers. However, changing the length of the buffer can be complicated. For instance, consider the case where we want to change the length of a slice for a DMA transfer:

use embedded_dma::{BufferExt, WriteBuffer};
struct DmaTransfer<Buf> {
    buf: Buf,
}

impl<Buf: WriteBuffer> DmaTransfer<Buf> {
    fn start(buf: Buf) -> Self {
        // DMA logic
        Self { buf }
    }

    async fn wait_until_done(&self) {
        // to implement
    }

    fn free(self) -> Buf {
        // busy loop which waits until DMA is done to ensure safety
        self.buf
    }
}

/// This function is bad because we cannot obtain the original slice—just a subset of it.
async fn dma_transfer_bad1(buffer: &'static mut [u8], length: usize) -> &'static [u8] {
    let sub_slice = &mut buffer[..length];
    let transfer = DmaTransfer::start(sub_slice);
    transfer.wait_until_done().await;
    transfer.free()
}

/// This function is bad because we cannot unsplit the slice.
async fn dma_transfer_bad2(buffer: &'static mut [u8], length: usize) -> &'static [u8] {
    let (slice_a, slice_b) = buffer.split_at_mut(length);
    let transfer = DmaTransfer::start(slice_a);
    transfer.wait_until_done().await;
    let slice_a = transfer.free();
    // can't unsplit!!!
    slice_a
}

/// This function is good—we can get the whole slice back!
fn dma_transfer(buffer: &'static mut [u8], length: usize) -> &'static [u8] {
    let buffer_slice = buffer.into_buffer_slice(..length);
    let transfer = DmaTransfer::start(buffer_slice);
    // we are commenting this out so we can actually test it without an async runtime
    // transfer.wait_until_done().await;
    let buffer_slice = transfer.free();
    buffer_slice.inner()
}

const SIZE: usize = 1024;

let buffer = {
    static mut BUFFER: [u8; 1024] = [0_u8; SIZE];
    unsafe { &mut BUFFER }
};

assert_eq!(buffer.len(), SIZE);

let returned_buffer = dma_transfer(buffer, 123);

assert_eq!(returned_buffer.len(), SIZE);

Considerations

andrewgazelka commented 2 years ago

@eldruin @thalesfragoso any thoughts?

thalesfragoso commented 2 years ago

Sorry, I haven't had the time to review this yet. However, I would like to say that I agree with the underlying reasoning and I know that tokio-uring has something similar, so maybe it's worth checking it, even if it's just to compare to.