Skip to content

[RFC] "rooting" memory (&'static mut- references in init) #47

@japaric

Description

@japaric

Background

I was revisiting the original use case that @pftbest once told me about -- a lock free single
producer single consumer ring buffer -- and that led me to submit #37 and noticed that RTFM still
can't fulfill that use case in 100% safe code. (I hope I'm wrong)

Below is a program that explains the use case:

#![deny(warnings)]
#![feature(proc_macro)]
#![no_std]

extern crate blue_pill;
extern crate cortex_m_rtfm as rtfm;
// https://github.com/japaric/spscrb
extern crate spscrb;

use blue_pill::stm32f103xx::{self, Interrupt};
use rtfm::{app, Threshold};
use spscrb::{Consumer, Producer, RingBuffer};

app! {
    device: stm32f103xx,

    resources: {
        static PRODUCER: Producer<u32, [u32; 32]>;
        static CONSUMER: Consumer<u32, [u32; 32]>;
    },

    tasks: {
        EXTI0: {
            path: exti0,
            resources: [PRODUCER],
            priority: 1,
        },

        EXTI1: {
            path: exti1,
            resources: [CONSUMER],
            priority: 2,
        },
    },
}

fn init(_p: init::Peripherals) -> init::LateResourceValues {
    let rb: &'static mut RingBuffer<u32, [u32; 32]>;

    // this part needs to be hidden from the user, or rather the user shouldn't need to do this
    rb = {
        static mut RB: RingBuffer<u32, [u32; 32]> = RingBuffer::new();
        unsafe { &mut RB }
    };

    // NOTE this method *consumes* a `&'static mut` reference
    let (p, c) = rb.spsc();
    // let (p2, c2) = rb.spsc();
    // ^ this would be an error

    init::LateResourceValues { PRODUCER: p, CONSUMER: c }
}

fn idle() -> ! {
    rtfm::set_pending(Interrupt::EXTI0);
    rtfm::set_pending(Interrupt::EXTI1);

    loop {
        rtfm::wfi();
    }
}

fn exti0(_t: &mut Threshold, r: EXTI0::Resources) {
    // lock-free operation
    r.PRODUCER.enqueue(0xdead_beef).unwrap();
}

fn exti1(_t: &mut Threshold, r: EXTI1::Resources) {
    // lock-free operation
    r.CONSUMER.dequeue().unwrap();
}

Basically you have a statically allocated ring buffer which is hidden from the user. To enqueue and
dequeue elements into the buffer you have to use the "producer" and "consumer" end points,
respectively. These end points can be used from different execution contexts, which can run at
different priorities, in a lock free manner. This is memory safe, without locking, because the
producer and the consumer, each, own different cursors into the ring buffer and because the cursors
are word sized (they can be atomically read) -- check the implementation for details.

To construct the producer and consumer there's this spsc method with signature fn(&'static mut RingBuffer<_, _>) -> ... This signature ensures that (a) the ring buffer is statically allocated,
and thus it will never be destroyed, and (b) that once you have called this method the reference to
the ring buffer becomes invalidated (required to avoid mutable aliasing).

Thanks to #43 (thanks @jonas-schievink!) we can defer initialization of resources and initialize the
producer and consumer end points in init (see example above). However, one problem remains:
there's no way to safely get / create a &'static mut RingBuffer<_, _> in init. The only place
where &'static mut - references are available right now is in idle but that's too late for
initialization of resources (without wrapping them in Option).

Here's my proposal for solving this:

Proposal

Add a roots field to app!. This field contains a list of static variables with initial values.
The syntax of its contents is similar to the contents of resources.

app! {
    roots: {
        static RB: RingBuffer<u32, [u32; 32]> = RingBuffer::new();
    },
}

The user will get a list of &'static mut- references to these roots in init:

// auto-generated: struct Roots { RB: &'static mut RingBuffer<u32, [u32; 32]>, }
fn init(roots: init::Roots, ..) {
    let (p, c) = roots.RB.spsc();
    // ..
}

Implementation

This can be implemented by creating a static mut variable for each root.

// auto-generated
fn main() {
    interrupt::free(|_| {
        static mut RB: RingBuffer<u32, [u32; 32]> = RingBuffer::new();

        init(init::Roots { RB: &mut RB }, ..);
    });
}

Alternatively, the roots could be allocated in the lowest / first stack frame (hence "rooting" in
the proposal name), which is known to never be deallocated: (I haven't tested if this works)

// auto-generated
fn main() {
    interrupt::free(|_| {
        let mut RB: RingBuffer<u32, [u32; 32]> = RingBuffer::new();
        let roots = init::Roots { RB: &mut *(&mut RB as *mut _) };
        mem::forget(RB); // ???

        init(roots, ..);

        // ..
    });

    // idle is a divergent function
    idle();
}

Other use cases

Another use case for having &'static mut- references in init is being able to set up periodic
DMA transfers in init. An example of this use case is setting up a circular DMA transfer that
reads ADC or Serial data:

app! {
    roots: {
        static BUFFER: [[u16; 64]; 2] = [[0; 64]; 2];
    },

    resources: {
        // cf. japaric/embedded-hal#14 and the blue-pill `dma` module
        static BUFFER: dma::CircBuffer<[u16; 64]>;
    }
}

fn init(roots: init::Roots, ..) -> init::LateResourceValues {
    let buffer = adc1.start(dma1, roots.BUFFER);

    init::LateResourceValues {
        BUFFER: buffer,
    }
}

Thoughts? Can these use cases be safely achieved through some other means?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions