2

On ARM Cortex M3 bare-metal real-time microcontroller application, I need to ensure that function dispatch() is called exactly once per interrupt. dispatch() uses global data and is hence not reentrant, so I also need to ensure that it does not get called in an ISR if it is already in the middle of running.

In pseudocode, I can achieve these goals like this:

atomic bool dispatcher_running;
atomic uint dispatch_count;

ISR 0:
    flag0 = true;
    dispatch_count++;
    if (!dispatcher_running)
         dispatch();
ISR 1:
    flag1 = true;
    dispatch_count++;
    if (!dispatcher_running)
         dispatch();
...
void dispatch() {
    if (dispatcher_running)     // Line A
        return;
    dispatcher_running = true;  // Line B
    while (dispatch_count--) {
       // Actual work goes here
    }
    dispatcher_running = false;
}

The challenge, however, is that dispatch_count and dispatcher_running need to be atomic or barriered. Otherwise, race conditions could result. Even pure atomic isn't enough: An ISR could call dispatch(), which passes the check at Line A, and, before it gets to Line B, another ISR calls dispatch(); this would result in two instances of dispatch running simultaneously.

My questions are:

  1. What type of atomic or barriers do I need to make this work?
  2. Which ARM instructions (e.g. DMB) can be used to achieve them?
  3. Is there a portable std C++ way to do something similar? I don't intend to use e.g. std::atomic, as this is bare metal. But I would like to have a similar C++ lib that does the equivalent, as I am prototyping parts of the application on a PC.
Frant
  • 5,382
  • 1
  • 16
  • 22
SRobertJames
  • 8,210
  • 14
  • 60
  • 107
  • 1
    `std::atomic::exchange` should inline to lock-free code, or write it in pure asm, same for compare_exchange_strong. On Cortex-M, you might need to make sure to run a `clrex` at the start of your interrupt handler to make sure getting interrupted inside the later LL/SC retry loop does actually make the `strex` fail. (Simple CPUs have a very simplistic "monitor", IIRC.) – Peter Cordes Mar 10 '22 at 23:22
  • @PeterCordes Do you mean simply do `dispatch() { bool expected = false; if (!dispatcher_running.compare_exhange_strong(expected, true) return; while (dispatch_count.fetch_sub(1))...}`, and that will work fine on bare metal? Cool. I don't see the use of the `std::atomic::exchange` you mentioned. – SRobertJames Mar 11 '22 at 00:58
  • If it compiles to ldrex/strex, then yeah it should, I think. (If I have the details of your problem correct in my head, and am correctly remembering how ARM works.) Check the asm to make sure it's lock-free and not calling a helper function. To use `exchange` instead of CAS, you simply swap in a `true` and check the old value. If the old value was already `true`, you didn't *make* it true, so some other level of interrupt has already "claimed" it and is in progress. Exactly like a try_lock attempt on a spinlock which can use CAS or exchange. – Peter Cordes Mar 11 '22 at 03:42
  • I'm pretty sure this is a case where you need to make sure you `clrex` or something before attempting the atomic RMW, though, so make sure you read up on how that works. – Peter Cordes Mar 11 '22 at 03:44

0 Answers0