embedded-dma
embedded-dma copied to clipboard
Add buffer slices
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]
- When it wraps a [ReadBuffer], it implements [ReadBuffer]
- When it wraps a [WriteBuffer], it implements [WriteBuffer]
- To prevent panics and to enforce safety, the given range is coerced between
(0, len]
, wherelen
is the length of the original buffer
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
- [ ] Should we include extension method
into_buffer_slice
or not?- An example where it might not look as aesthetically pleasing is
let buf_slice = (&BUF).into_buffer_slice(123..);
since we have to add ()'s around&BUF
- [ ] If we do include this, should we only have
into_buffer_slice
implemented for Read and Write Buffers, not for all sized structs? We could instead have methodsinto_{write, read}_buffer_slice
.
- An example where it might not look as aesthetically pleasing is
@eldruin @thalesfragoso any thoughts?
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.