Access control

One of the core foundations of RTFM is access control. Controlling which parts of the program can access which static variables is instrumental to enforcing memory safety.

Static variables are used to share state between interrupt handlers, or between interrupts handlers and the bottom execution context, main. In normal Rust code it's hard to have fine grained control over which functions can access a static variable because static variables can be accessed from any function that resides in the same scope in which they are declared. Modules give some control over how a static variable can be accessed by they are not flexible enough.

To achieve the fine-grained access control where tasks can only access the static variables (resources) that they have specified in their RTFM attribute the RTFM framework performs a source code level transformation. This transformation consists of placing the resources (static variables) specified by the user inside a const item and the user code outside the const item. This makes it impossible for the user code to refer to these static variables.

Access to the resources is then given to each task using a Resources struct whose fields correspond to the resources the task has access to. There's one such struct per task and the Resources struct is initialized with either a mutable reference (&mut) to the static variables or with a resource proxy (see section on critical sections).

The code below is an example of the kind of source level transformation that happens behind the scenes:


# #![allow(unused_variables)]
#fn main() {
#[rtfm::app(device = ..)]
const APP: () = {
    static mut X: u64: 0;
    static mut Y: bool: 0;

    #[init(resources = [Y])]
    fn init(c: init::Context) {
        // .. user code ..
    }

    #[interrupt(binds = UART0, resources = [X])]
    fn foo(c: foo::Context) {
        // .. user code ..
    }

    #[interrupt(binds = UART1, resources = [X, Y])]
    fn bar(c: bar::Context) {
        // .. user code ..
    }

    // ..
};
#}

The framework produces codes like this:

fn init(c: init::Context) {
    // .. user code ..
}

fn foo(c: foo::Context) {
    // .. user code ..
}

fn bar(c: bar::Context) {
    // .. user code ..
}

// Public API
pub mod init {
    pub struct Context<'a> {
        pub resources: Resources<'a>,
        // ..
    }

    pub struct Resources<'a> {
        pub Y: &'a mut bool,
    }
}

pub mod foo {
    pub struct Context<'a> {
        pub resources: Resources<'a>,
        // ..
    }

    pub struct Resources<'a> {
        pub X: &'a mut u64,
    }
}

pub mod bar {
    pub struct Context<'a> {
        pub resources: Resources<'a>,
        // ..
    }

    pub struct Resources<'a> {
        pub X: &'a mut u64,
        pub Y: &'a mut bool,
    }
}

/// Implementation details
const APP: () = {
    // everything inside this `const` item is hidden from user code

    static mut X: u64 = 0;
    static mut Y: bool = 0;

    // the real entry point of the program
    unsafe fn main() -> ! {
        interrupt::disable();

        // ..

        // call into user code; pass references to the static variables
        init(init::Context {
            resources: init::Resources {
                X: &mut X,
            },
            // ..
        });

        // ..

        interrupt::enable();

        // ..
    }

    // interrupt handler that `foo` binds to
    #[no_mangle]
    unsafe fn UART0() {
        // call into user code; pass references to the static variables
        foo(foo::Context {
            resources: foo::Resources {
                X: &mut X,
            },
            // ..
        });
    }

    // interrupt handler that `bar` binds to
    #[no_mangle]
    unsafe fn UART1() {
        // call into user code; pass references to the static variables
        bar(bar::Context {
            resources: bar::Resources {
                X: &mut X,
                Y: &mut Y,
            },
            // ..
        });
    }
};