rtic-rs / rfcs

11 stars 6 forks source link

Binding panic handlers #27

Open adamgreig opened 4 years ago

adamgreig commented 4 years ago

Apologies if this is the wrong repository for the issue.

It would be really useful to be able to bind a task to the panic handler, allowing shared resources to be used during a panic. Maybe this is really hard to make sound, I'm not sure, but it would make it much easier to handle making systems safe and emitting diagnostics at panic. I appreciate that for a lot of people the various panic handler crates are used (panic-halt, panic-semihosting, etc) which wouldn't really tie in to RTFM, but I often need a custom panic handler to ensure hardware safety, which would be easier if it could integrate into RTFM.

japaric commented 4 years ago

Letting the panic handler use (shared) resources would be very weird, I think.

In the first place, you can call the panic handler from the NMI (Non-Maskable-Interrupt) or HardFault. These are highest priority, unmaskable exceptions: meaning that priority based masking can't protect the data.

The other issue is that the panic handler always can be called from anywhere. Because of this we cannot uphold the "once a resource is locked, all tasks that may access the resource cannot start" property, which is required for deadlock freedom. For example, this code would deadlock:

#[task(resources = [x])]
fn foo(cx: foo::Context) {
    x.lock(|x| {
        panic!();
    });
}

#[panic(resources = [x])]
fn panic(cx: panic::Context) -> ! {
    x.lock(|x| {
        // deadlock? this results in a nested lock
    });
}

What would be OK, I think, is letting the panic handler use owned resources, e.g. late-initialized data. Never mind, this is also unsound because NMI and HardFault cannot be masked; see below:

#[rtfm::app]
const APP: () = {
    struct Resources { x: u64 }

    #[panic(resources = [x])]
    fn panic(cx: panic::Context) -> ! {
        // exclusive access
        let x: &mut u64 = cx.x;

        // trigger NMI or a HardFault here
    }
}

// NOTE: not controlled by RTFM
#[exception]
fn NMI() {
    panic!(); // results in two exclusive references (`&mut-`) to the resource `x`
}

TL;DR because, in general, no-std panics can be nested the only shared data they can safely access are plain static variables, which must be Sync. So things like AtomicUsize or spin::Mutex can be safely access from panic handlers (and from HardFault and NMI).