Skip to content

Add buffer slices #20

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

andrewgazelka
Copy link
Contributor

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], where
    len 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 methods into_{write, read}_buffer_slice.

@andrewgazelka
Copy link
Contributor Author

@eldruin @thalesfragoso any thoughts?

@thalesfragoso thalesfragoso mentioned this pull request Feb 1, 2022
@thalesfragoso
Copy link
Member

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.

src/slice.rs Outdated
Comment on lines 164 to 168
fn coerced_range(&self, len: usize) -> Range<usize> {
let start = self.range.start.min(len);
let end = self.range.end.min(len);
Range { start, end }
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't an incorrect range be a reason to panic instead of silently attempting to correct the mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I changed the API to behave like the get method of a slice.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure what the most common use case will be, but I thought getting an invalid range always the user made a programming error. In that case it doesn't really make sense to have all these Option<> wrappers. I would have expected it to be behave like buf[start..end] which will panic if it's out of bounds.

I'm actually not sure what would be the most practical solution here. Maybe someone else should comment on this.

Copy link
Contributor Author

@andrewgazelka andrewgazelka Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean there is a non-panicking buf.get(start..end) method and a panicking buf[start..end]. Perhaps we could have both? I am trying to emulate regular slices as much as possible, and I feel you could argue buf.get(start..end) isn't needed because it is always user error as well. However, I think the non-panicking alternative might be useful in code which should not panic, or to more easily handle cases of overflows. I agree, I'd be interested in seeing what others think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eldruin I looked further into this. We would need four extension functions

into_write_buffer_slice
into_write_buffer_slice_checked
into_read_buffer_slice_checked
into_read_buffer_slice

or something like that. This seems quite verbose compared to just having two that can accomplish the same thing with than added .unwrap().

I am not sure a slice-like API is as easy as I thought it would be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants