Functions VS ISR
Have you ever thought why interrupt service routines(ISR) in some tool-chains is written in a special format that is different from the normal C functions? to answer this question we should first know the difference between normal C functions and interrupt service routines.
Normal C Functions
A function is a group of statements that together perform a task.This function is executed when it is called from a certain point in our code.so this means that in functions there is what we call a caller and callee. since functions is called at certain points of our code this means that functions are synchronized to the call which means functions are synchronous.
Interrupt service routines
On the other hand ISR is a group of statements that is executed in response to an event that leads to a change in the program execution.the events are asynchronous which means those events could happen at any time we don't know when they will occur and so ISR is asynchronous. but what is the effect of an ISR being asynchronous?
Effect of asynchronous property of ISR
To show the effect this property on code behavior. follow up with this assumption. imagine you have a function that takes two numbers add them together and return the result what would you expect will happen if an ISR occurred while the function was at the middle of executing the function? to know what will happen lets see the assembly code of the function and observe what would happen if an interrupt occurred at certain point of execution.
the above assembly code is just explained as follows the two numbers is loaded into two registers and added so now what will happen if an interrupt occurred at the second line?the catch is the CPU will wait to finish the instruction which means Register R24 is now loaded with the number we want to add but after that the ISR will be executed the ISR could actually change the content of Register R24 which contains the number we want to add and after it returns the addition operation would be wrong as the number changed by the ISR!!! so how Compiler deal with this issue so it don't happen? yes this will lead us to why ISR is written in a different format than normal function this is because compiler generate special code for ISR that allows it to store the context of most CPU registers so when the ISR finishes its execution the CPU context is returned and the function that was executing won't be affected by the ISR at all as the context that the function was acting on is restored from the ISR as it is.
so the difference here is that compiler will generate different code from code it will generate to normal functions in order to do the following
1-store most CPU registers (more than what is saved in functions) by pushing them into stack
2-adding a return from interrupt instruction at end of ISR to pop all saved registers from stack. so the return instruction form ISR is different form a return from function.
As an example for that we could observe the standard that of AVR GCC tool chain and how it says that ISR should have a different code than normal functions.
AVR-GCC Context switching
As we could observe that in GCC the registers mentioned here is allowed to be changed and clobbered by normal functions without having to be stored while at the same time those registers is not allowed to be clobbered by ISR and have to be saved so as we said the code written for ISR would include additional Registers to be saved and pushed into the stack and that's why ISR in GCC has an attribute to show that it is not a normal function.
to prove that this case is highly platform dependent i will discuss next how ARM managed to make ISR as a normal C functions without any overhead of adding other code or writing any additional attributes.
How Arm managed to make ISR normal C functions
So logically after we have seen how ISR is different for normal functions in two ways which is the number of saved registers and the special return instruction in order to make a normal c function able to be used for ISR the function should normally match the two conditions and that is what we will see how ARM managed to do.
How C functions work in ARM
C compilers for ARM architecture follow a specification from ARM called the AAPCS, Procedure Call Standard for ARM Architecture (reference 13). According to this standard, a C function can modify R0 to R3, R12, R14 (LR), and PSR. If the C function needs to use R4 to R11, it should save these registers on to the stack memory and restore them before the end of the function.R0-R3, R12, LR, and PSR are called “caller saved registers.” The program code that calls a subroutine needs to save these register contents into memory (e.g., stack) before the function call if these values will still be needed after the function call. R4-R11 are called “callee-saved registers.” The subroutine or function being called needs to make sure the contents of these registers are unaltered at the end of the function (same value as when the function is entered). The values of these registers could change in the middle of the function execution, but need to be restored to their original values before function exit.
Exception entry mechanism in ARM
In order to allow a C function to be used as an exception handler, the exception mechanism needs to save R0 to R3, R12, LR, and PSR at exception entrance automatically, and restore them at exception exit under the control of the processor’s hardware. In this way when returned to the interrupted program, all the registers would have the same value as when the interrupt entry sequence started. In addition, since the value of the return address (PC) is not stored in LR as in normal C function calls (the exception mechanism puts an EXC_RETURN code in LR at exception entry, which is used in exception return), the value of the return address also needs to be saved by the exception sequence. So in total eight registers need to be saved during the exception handling sequence on the Cortex-M3 or Cortex-M4 processors without a floating point unit.
Senior Embedded Software Engineer at eJad | PSM
4 年Very good article Omar ??