Static array ownership and move


#1

I am working on a 769 bit shift register that receives data using Spi. Multiple IC’s are chained together so that you now have a n * 769 bit register. I am trying to send data using Spi DMA. One of the problem I am facing is that since the register length is not a multiple of 8, the data here (RGB 3 * 8bit) needs to either packed together or I need to split the dma transfers such that I write 768 bits using
(DMA for 768 bits + convert Spi pins to IO + toggle bit) * Number of IC’s.

This is my effort at trying to do the above. Github PR

spi dma implementation

/// This function is a trivial version. The dma actually is implemented as 
// it takes ownership of the data and returns it later. I just wanted to atleast get this simple version working. 
fn dma(_data: &'static mut [u8])  {
    
}


pub struct Tlc5955<B>
where B: Unsize<[u8]> + 'static
{
    data: Option<&'static mut B>,
    current_ic: u8,
}


impl<B> Tlc5955<B>
where B: Unsize<[u8]>
{
    fn new() -> Self {
        Tlc5955 {
            data: None
           current_ic:0,
        }
    }
    
    fn update(&mut self, bytes: &'static mut B) {
        self.data = Some(bytes);
    }
    
    
    fn send(&mut self) {
        
        // Doesnt work.  I want to split the buffer into chunks of 768 and pass it to dma function, every time send gets called.
        self.data.and_then(|buffer| {
            dma(buffer[current_ic..current_ic + 1 * 768);
            Some(9)
        });
    }
}


static mut array: [u8;6] = [0;6];


fn main() {
    let mut t = Tlc5955::new();
    unsafe {t.update(&mut array);}
    t.send();
    
}

#2

You can’t pass ownership of a part of an array (that’s because ownership is tied to where allocations and deallocations are inserted into the code, and it’s the same problem as trying to split one malloc() into multiple blocks that are free()d).

You could use Option.take() to pass ownership of the whole object and pass a range separately. Or if dma() doesn’t absolutely require ownership, then keep it using slices. You can use &buffer[…] and split_at_mut() to get multiple slices out of a single array.


#3

The move is basically used to ensure that you do not write into the buffer that is already being modified by the DMA. Probably there are better ways of doing the same.


#4

&mut ensures the same exclusivity. There cannot be two &mut references anywhere in the entire program that point to the same memory at the same time.


#5

But does just &mut without 'static ensure that the object lives long enough for the dma to work on it, since this is going to be async call.


#6

If it compiles, then yes, it guarantees it lives long enough :slight_smile: [assuming your dma function works within Rust rules and finishes using the data before returning]

If dma returns before finishing using the data, then unfortunately it’s not safe, and it should take ownership of the whole array.


#7

@kornel I am a bit skeptical, because this is like having two different processors, with one working in the background


#8

OK, so you have an unsafe function here.


#9

Yup


#10

&'static mut [u8] means you’re borrowing the slice for the life of the program, which is unlikely what you want. Instead, you’re really looking for something like &'a mut &'static mut [u8], which is a temporary borrow of a mutable slice that lives forever, roughly:

static mut array: [u8; 6] = [0; 6];

fn main() {
    let mut a: &'static mut [u8];
    unsafe {
        a = &mut array;
    }
    bar(&mut a);
}

The a binding has a lifetime itself now, and you could treat it like a value - borrow it mutably for some shorter scope, and prevent multiple borrows at the same time.

However, you have native code that’s async and ultimately you’ll need to use unsafe code anyway. Maybe you could build a future-like API that you could use to gate access to subsequent dma calls. Or build some mutex-like abstraction that blocks the caller until in-flight dma completes. Just throwing out random ideas :slight_smile:


#11

@vitalyd I will try that suggestion. The dma code is designed like a future. The idea is that the dma takes control of the buffer and returns it in the interrupt when its done.


#12

Could you use a heap-allocated array (i.e. Box<[u8]>) and pass actual ownership of it to the DMA engine, and then get ownership back when the future (like thing) completes?


#13

Its a microcontroller, so no heap :slight_smile:


#14

Ah, yeah :man_facepalming: