How to share peripheral register structs with interrupt handlers?

Does anyone have advice about the current best practices for sharing Peripheral Access Crate register structs between an application’s main thread and its interrupt handlers? So far, I’ve come up with two working solutions, but neither seems ideal.

Option 1: Use a static mut variable, e.g.:

// (no_std, crate includes, etc)
static mut timer : stm32::TIM6 = stm32::TIM6 { _marker: PhantomData };

#[entry]
fn main() -> ! {
  let periphs = stm32::Peripherals::take()::unwrap();
  unsafe { let timer = periphs.TIM6 };
  // ( NVIC and timer setup )
}

#[interrupt]
unsafe fn TIM6_DAC_LPTIM1() {
  timer.sr.modify( |_r,w| w.uif().clear_bit() );
  // ( Do stuff )
}

This doesn’t seem great, though. It’s fine if these shared registers are always considered unsafe, but the _marker fields generated by svd2rust are private. So this approach requires modifying the auto-generated Peripheral Access Crate to make that field public so that the initial ‘empty’ value can be declared.

Option 2: Use an Option. This ended up being a little tricker than you might expect, because the compiler throws “cannot move out of static item” errors when you try to call something like .unwrap() on a static mutable Option.

static mut TIMER : Option< stm32::TIM6 > = None;
unsafe fn get_timer() -> &'static mut stm32::TIM6 {
  match TIMER {
    Some( ref mut tim ) => &mut *tim,
    None => panic!(),
  }
}

fn main() -> ! {
  let periphs = stm32::Peripherals::take()::unwrap();
  unsafe { let timer = Some( periphs.TIM6 ) };
  // ( NVIC and timer setup )
}

#[interrupt]
unsafe fn TIM6_DAC_LPTIM1() {
  let timer = get_timer();
  timer.sr.modify( |_r,w| w.uif().clear_bit() );
  // ( Do stuff )
}

This looks more Rust-y, but it requires a function call every time that you want to retrieve the peripheral register struct, and that seems like it might be inefficient. Maybe a macro would be more appropriate?

I dunno, I’m pretty new to Rust; sorry if this is an obvious question, but I couldn’t find an answer in the FAQ or by searching.

1 Like

This is really hard to do correctly in any language – Rust happens to make that a lot more obvious. :wink:

The modify operation provided by the drivers you’re using is (last I checked) not atomic. This means that any race between an interrupt handler and your main thread accessing the same register can produce surprising results.

The static mut Option doesn’t actually fix this (it is also not atomic), or the real issue with the static mut: You still run the risk of producing an &mut in two places at once, which is instant undefined behavior.

Here are some options for doing this safely. (Note that these are essentially the same options you’d use in C, but the results are more robust.)

  1. Use a mutex, specifically one that contains the data it guards. Put your option in there. Lightweight mutexes provided by embedded crates are usually critical section based, so this is a convenient alternative to number 2 below. This has the advantage that, if one of your crates provides a mutex, you don’t need to write unsafe code.

  2. Use a critical section around your existing static mut code, and be very very careful not to let the &mut escape it. This requires unsafe code.

  3. Ensure that your interrupt handler and main thread access separate registers. You can get the borrow checker’s help on this by handing a &mut to one of the register fields to the interrupt handler – any attempt to access that register from the thread while the reference lives will produce a compile error. The process of sending the reference to the interrupt handler is pretty involved, however, and requires one of the above techniques under the hood. See my hack for this in m4vga.

I am skipping some more involved options exploiting the interrupt priority scheme of the processor you’re using (Cortex-Ms are quite good at this sort of thing).

1 Like

Thank you for the reply! Sorry if I wasn’t clear about the question; I am aware of the dangers involved in performing read-modify-writes to registers in parallel.

What I meant was, assuming that the developer ensures that the registers are never actually accessed concurrently, what is the best syntax for sharing these singleton values between a main thread and interrupt handlers?

Thanks, and sorry if there is an obvious way to do this in Rust. I’m still learning how to apply embedded C concepts to the language.

No worries, this is a great question.

My first suggestion would be the one I’d, well, suggest. Here is the Mutex type that you have access to.. It is implemented as a critical section, so there’s no risk that your interrupt handler will fail to acquire the mutex. Because it stores state internally, you can stuff your Option<PeripheralRegisters> into it the same way you’ve done with your static mut, only without needing unsafe code.

There’s a great, short example in the concurrency chapter of the Rust Embedded book, which you should read if you haven’t.

Oh, thanks! That’s just what I was looking for. Sorry, I’d been looking in the ‘interrupts’ and ‘exceptions’ sections of the book.

Is there a good way to do this using the core libraries without critical sections, though? They aren’t really suitable for realtime code, and I’d like to avoid disabling interrupts every time that a shared peripheral register is accessed. I’m not too worried about using unsafe blocks where the program logic ensures that the resources won’t be accessed concurrently, but I am worried about the runtime impact of critical sections.

That’s the only safe way I’m aware of, short of something like the IRef type I linked you to (and I think it’s probably fragile, but it covers my specific use case). You’ve already discovered the other way. :wink:

To geek out on your particular processor:

Critical sections don’t have to hurt interrupt latency on CortexM, because masking interrupts actually only affects interrupts beneath some priority. Doesn’t look like the rust embedded Mutex exposes this, though. I’d glance around for any alternative that says something like “priority ceiling.”

Cool, makes sense. The Cortex-M interrupts do seem like a bit of a rabbit hole, and I definitely still have a lot to learn about them. Thanks again for the help! :slight_smile:

The resource system in cortex-m-rtfm understands interrupt priorities and should be able to do the right thing.

True, if you want to write your app in terms of such a framework, RTFM will totally handle it.

Critical Sections are really light weight. The only thing bad with them is that they make the worst-case-execution time of an interrupt handler a lot harder or even impossible to calculate. In 99% of the “realtime” cases this is not a problem and most other implementations (like in C) also implement tasks in the exact same way.

Per default all interrupts have the same priority so preemption cannot happen anyway which means not using critical sections is also fine (as long as you’re exclusively interrupt driven).

If you want fancy non-blocking resource sharing you should look into using RTFM.

1 Like