Search code examples
armatomiccortex-mload-link-store-conditional

When is CLREX actually needed on ARM Cortex M7?


I found a couple of places online which state that CLREX "must" be called whenever an interrupt routine is entered, which I don't understand. The docs for CLREX state (added the numbering for easier reference):

(1) Clears the local record of the executing processor that an address has had a request for an exclusive access.

(2) Use the CLREX instruction to return a closely-coupled exclusive access monitor to its open-access state. This removes the requirement for a dummy store to memory.

(3) It is implementation-defined whether CLREX also clears the global record of the executing processor that an address has had a request for an exclusive access.

I don't understand pretty much anything here.

I had the impression that writing something along the lines the example in the docs was enough to guarantee atomicity:

    MOV r1, #0x1                ; load the ‘lock taken’ value
try:                                                       <---\
    LDREX r0, [LockAddr]        ; load the lock value          |
    CMP r0, #0                  ; is the lock free?            |
    STREXEQ r0, r1, [LockAddr]  ; try and claim the lock       |
    CMPEQ r0, #0                ; did this succeed?            |
    BNE try                     ; no - try again   ------------/
    ....                        ; yes - we have the lock
  1. Why should the "local record" need to be cleared? I thought that LDREX/STREX are enough to guarantee atomic access to an address from several interrupts? I.e. GCC for ARM compiles all C11 atomic functions using LDREX/STREX and I don't see CLREX being called anywhere.

  2. What "requirement for a dummy store" is the second paragraph referring to?

  3. What is the difference between the global record and a local record? Is global record needed for multi-core scenarios?


Solution

  • Taking (and paraphrasing) your three questions separately:

    1. Why clear the access record?

    When strict nesting of code is enforced, such as when you're working with interrupts, then CLREX is not usually required. However, there are cases where it's important. Imagine you're writing a context switch for a preemptive operating system kernel, which can asynchronously suspend a running task and resume another. Now consider the following pathological situation, involving two tasks of equal priority (A and B) manipulating the same shared resource using LDREX and STREX:

    Task A      Task B
      ...
     LDREX
    -------------------- context switch
                 LDREX
                 STREX   (succeeds)
                  ...
                 LDREX
    -------------------- context switch
     STREX               (succeeds, and should not)
      ...
    

    Therefore the context switch must issue a CLREX to avoid this.

    2. What 'requirement for a dummy store' is avoided?

    If there wasn't a CLREX instruction then it would be necessary to use a STREX to relinquish the exclusive-access flag, which involves a memory transaction and is therefore slower than it needs to be if all you want to do is clear the flag.

    3. Is the 'global record' for multi-core scenarios?

    Yes, if you're using a single-core machine, there's only one record because there's only one CPU.