I am in the process of implementing an SPI driver.
Now I have the following driver code (MRE-ified):
use tokio::{join, sync::mpsc};
async fn spi_transmit(write_buf: &[u32], read_buf: &mut [u32]) {
assert_eq!(read_buf.len(), write_buf.len());
let (write_fifo, mut read_fifo) = mpsc::channel(2);
let write_task = async {
// Simulate an SPI bus that respondes with the sent data + 20,
// just for demo purposes
for val in write_buf {
write_fifo.send(*val + 20).await.unwrap();
}
};
let read_task = async {
for val in read_buf {
*val = read_fifo.recv().await.unwrap();
}
};
join!(write_task, read_task);
}
#[tokio::main]
async fn main() {
let buf_out = [1, 2, 3, 4];
let mut buf_in = [0, 0, 0, 0];
spi_transmit(&buf_out, &mut buf_in).await;
println!("{:?}", buf_in);
}
[21, 22, 23, 24]
The central API is async fn spi_transmit(write_buf: &[u32], read_buf: &mut [u32]).
Now I need to implement an async fn spi_transmit_in_place(read_write_buf: &mut [u32]) that transmits and receives from the same buffer, which in the end contains the read data.
I know for a fact that read and write access to the buffer will never overlap, a byte will always be read first and then at a later, non-overlapping time it will be written.
With that knowledge, is it considered sound if I implement it like this, or is this considered undefined behaviour?
async fn spi_transmit_in_place(read_write_buf: &mut [u32]) {
let write_buf = unsafe {
let data = read_write_buf.as_ptr();
let len = read_write_buf.len();
std::slice::from_raw_parts(data, len)
};
spi_transmit(write_buf, read_write_buf).await
}
#[tokio::main]
async fn main() {
let mut buf = [1, 2, 3, 4];
spi_transmit_in_place(&mut buf).await;
println!("{:?}", buf);
}
[21, 22, 23, 24]
It works, but miri is not happy.
If this is undefined behavior, does this mean I’m forced to re-implement spi_transmit using raw pointers? Or how else could I solve this problem?
>Solution :
Yes, about having both a reference and an exclusive reference Behavior considered undefined tells us:
breaking the pointer aliasing rules
is UB if both references are live, specifically
Such ranges shall not overlap with any ranges of addresses allocated by mechanisms provided by LLVM
and
Each time a reference or box is passed to or returned from a function, it is considered live.
creating them and then sequentially accessing them one after the other also makes them live from creation to access and thus you don’t even need to call a function to make it UB.