0
votes

i will write my assumptions (based on my researches) in the question below i assume that there are mistakes in my assemptions outside the question it self:

i'm looking into some code written for ARM:

this function (taken from FreeRTOS port code):

portFORCE_INLINE static uint32_t ulPortRaiseBASEPRI(void)
{
    uint32_t ulOriginalBASEPRI, ulNewBASEPRI;

    __asm volatile("    mrs %0, basepri                                         \n"
                   "    mov %1, %2                                              \n"
                   "    msr basepri, %1                                         \n"
                   "    isb                                                     \n"
                   "    dsb                                                     \n"
                   : "=r"(ulOriginalBASEPRI), "=r"(ulNewBASEPRI)
                   : "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

    /* This return will not be reached but is necessary to prevent compiler
    warnings. */
    return ulOriginalBASEPRI;
}

i understand in gcc "=r" is output operand. so we save values from asm to C variable

now the code in my understanding is equivalent to:

ulOriginalBASEPRI = basepri
ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY
basepri = ulNewBASEPRI

i understand we are returning the original value of BASEPRI so thats the first line. however, i didn't understand why we assign variable ulNewBASEPRI then we use it in MSR instruction..

so I've looked in the ARMV7 instruction set and i saw this: enter image description here

i assume there is no (MSR immediate) in thumb instruction and "Encoding A1" means its only in Arm instruction mode. so we have to use =r output operand to let asembler to auto select a register for our variable am i correct?


EDIT: ignore this section because i miscounted colons

: "i"(configMAX_SYSCALL_INTERRUPT_PRIORITY));

from my understanding for assembly template:

   asm ( assembler template 
       : output operands                  /* optional */
       : input operands                   /* optional */
       : list of clobbered registers      /* optional */
       );

isn't "i" just means (immediate) or constant in the assembly?
does this mean the third colon is not only for clobber list?
if that so, isn't it more appropriate to find the constraint "i" in the input operands?
EDIT: ignore this section because i miscounted colons


i understand isb, dsb are memory barrier stuff but i really dont understand the discription of them. what they really do? what happen if we remove dsb or isb instruction for example.?

2
I think you may have miscounted the number of colons here. "i" is an input. ulOriginalBASEPRI & ulNewBASEPRI both come after the first colon and are thus outputs. configMAX_SYSCALL_INTERRUPT_PRIORITY comes after the second colon and so is an input. This example has no clobbers.David Wohlferd
@DavidWohlferd, ah yeah thats rights i miscounted !!Hasan alattar

2 Answers

2
votes

so we have to use =r output operand to let assembler to auto select a register for our variable am i correct?

Yes, but it's the compiler that does register allocation. It just fills in the %[operand] in the asm template string as a text substitution and feeds that to the assembler.

Alternatively, you could hard-code a specific register in the asm template string, and use a register-asm local variable to make sure an "=r" constraint picked it. Or use an "=m" memory output operand and str a result into it, and declare a clobber on any registers you used. But those alternatives are obviously terrible compared to just telling the compiler about how your block of asm can produce an output.


I don't understand why the comment says the return statement doesn't run:

   /* This return will not be reached but is necessary to prevent compiler
   warnings. */
   return ulOriginalBASEPRI;

Raising the basepri (ARM docs) to a higher number might allow an interrupt handler to run right away, before later instructions, but if that exception ever returns, execution will eventually reach the C outside the asm statement. That's the whole point of saving the old basepri into a register and having an output operand for it, I assume.

(I had been assuming that "raise" meant higher number = more interrupts allowed. But Ross comments that it will never allow more interrupts; they're "raising the bar" = lower number = fewer interrupts allowed.)

If execution really never comes out the end of your asm, you should tell the compiler about it. There is asm goto, but that needs a list of possible branch targets. The GCC manual says:

GCC assumes that asm execution falls through to the next statement (if this is not the case, consider using the __builtin_unreachable() intrinsic after the asm statement).

Failing to do this might lead to the compiler planning to do something after the asm, and then it never happening even though in the source it's before the asm.


It might be a good idea to use a "memory" clobber to make sure the compiler has memory contents in sync with the C abstract machine. (At least for variables other than locals, which an interrupt handler might access). This is usually desirable around asm barrier instructions like dsb, but it seems here we maybe don't care about being an SMP memory barrier, just about consistent execution after changing basepri? I don't understand why that's necessary, but if you do then worth considering one way or another whether compile-time reordering of memory access around the asm statement is or isn't a problem.

You'd use a third colon-separated section in the asm statement (after the inputs) : "memory"

Without that, compilers might decide to do an assignment after this asm instead of before, leaving a value just in registers.

// actual C source
  global_var = 1;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  global_var = 2;

could optimize (via dead-store elimination) into asm that worked like this

// possible asm
  global_var = 2;
  uint32_t oldpri = ulPortRaiseBASEPRI();
  // or global_var = 2; here *instead* of before the asm
2
votes

Concerning ARM/Thumb instruction set differences on msr: you should be able to answer this yourself from the documentation. ;-) It is just 2 pages later. Edit: Chapter A8.1.3 of the linked manual clearly states how encodings are documented on instructions.

dsb (data synchronization barrier) makes sure that all memory accesses are finished before the next instruction is executed. This is really shortly written, for the full details you need to read the documentation. If you have further specific questions about this operation, please post another question.

isb (instruction synchronization barrier) purges the instruction pipeline. This pipeline buffers instructions which are already fetched from memory but are not yet executed. So the next instruction will be fetched with possibly changed memory access, and this is what a programmer expects. The note above applies here, too.