I have a question about the correct setting of the Cortex-M interrupt priorities in CMSIS-RTOS RTX. The CMSIS-RTX online manual (for the latest version 4.78) does not talk about this issue, and in fact, does not explain at all how to write ISRs for CMSIS-RTX.
Correct setting of the Cortex-M interrupt priorities is important, because the CMSIS-RTOS RTX kernel apparently does not disable interrupts explicitly. Instead, the atomic execution of critical sections is implemented as the SVC (Supervisor Call) exception, which runs at priority higher than any task.
Specifically, the priority of the SVC exception is set in the NVIC to the lowest possible level (0xFF), so is the priority of the PendSV and SysTick exceptions. With this setting, the exceptions cannot preempt each other, so their execution is mutually exclusive. So far so good.
But, what about the application-specific interrupts. It seems that they too need to be prioritized in the NVIC at the lowest level (0xFF), because any other setting would allow the IRQs to preempt the SVC/PendSV exceptions and could lead to corruption of the internal RTX state.
Is this observation accurate?
If so, then RTX would essentially defeat the purpose of the NVIC ("nested" interrupt controller), as the interrupts would NOT be allowed to nest.
Second, the CMSIS-RTX users must be made aware that they need to manually adjust the priorities of all their interrupts to 0xFF, by calling NVIC_SetPriority(<irq>_IRQn, 0xFFU) for all their interrupts. This is necessary, because all IRQs have priority 0 (the highest) out of reset, and with this setting they would be allowed to preempt RTX critical sections.
And finally, the whole design of CMSIS-RTOS RTX around the SVC exception makes most of the RTOS code execute in the exception context of the Cortex-M processor. This immensely hinders debugging, because you lose the call-stack context. In fact, the whole RTOS code feels like debugging a humongous ISR. This is exactly counter to the best practice to "keep your ISRs short".
Any comments about these issues would be highly appreciated.
Miro Samek state-machine.com
Writing ISR's for CMSIS-RTX or even just bare metal does take some understanding of what is going on.
There is no need to limit the Priority of any of the ISR's save the ones you mentioned. SVC,Systick and PendSV. They should be the lowest priority and any interrupt on the system.
Looking at the RTX code you will see any (ok almost any) function that is called within an isr does not actually execute the RTX code. All it does is place an item into a circular buffer. This circular buffer is ISR safe as it uses exclusive access to the index. This is a small amount of time needed to make sure things are safe.
It is actually one of the 3 interrupts listed above that actually run the code for these RTX items (I believe the PendSV as that makes the most sense to me - and IF all 3 of those interrupts are pending it will be the last to execute.
Every example that I have seem Keil provide very specifically sets both the interrupt Group to be used in the system and the priorities for any used Interrupt. This is not really any different for RTX, CMSIS-RTX or bare metal programming. The User really should always set these.
Yes, "most" of the RTX code does execute at exception level. That is clearly the intent by the way it was designed. None of the functions seem to be long at all. Not sure where this "Humongous ISR" term is comming from. My RTX code usually complies into less than 2K of code space with -o1. Since it is likely that any other interrupt is a higher priority than any executing RTX/CMSIS-RTX code (unless of course you actually DO set their priorities to 0xFF)
I don't believe you should be spending much time at all debugging the RTX code. It is fine the way it is. There are minor issues with the CMSIS-RTX that need to be worked around, but that is not really an RTX issues, it is more an CMSIS definition that at times is somewhat ambiguous. Just try and build translation layer between CMSIS and your favorite RTOS and you will run into a few of these that will take time to ponder and the pondering may produce something that the original spec writers did not intened.
Thank you very much for your comments. This is very helpful.
If you say that the RTX code is free from race conditions, even if nesting of interrupts is allowed, I will need to take your word for it.
It just was not something that was immediately obvious to me. For example, I was trying to follow what happens after osSignalSet() API is called from an ISR, and got a bit lost in at least 5 levels of function calls and a lot of global variables touched in between. All this didn't strike me as code that is patently "ISR-safe". But, let's assume that it is.
On a different subject, I have also a question about the correct setup for the hardware FPU in Cortex-M4F. The standard CMSIS startup code seems to leave the FPU with automatic FPU state preservation as well as the FPU lazy stacking enabled. It seems to me that both these features should be disabled for CMSIS-RTX, but I have not seen code that would to this. Could you perhaps shed some light on the proper FPU setup for RTX?
Again, thanks a lot for your help.
The FPU setting is up to you. It is actually by task. Some tasks can use it and others do not have to use it. If a task has it enabled, the micro will automatically save the lower set of FPU registers and the HAL_CM4.c file you can see looks for the flag and if it is set it will save/restore the high registers.
Yes, the nature of the OS calls from an ISR is not apparent at all, especially if you have some preconceived notion of what you are looking for.
You can look at the RTX code (it is best to look at that first, and then the "shim" between CMSIS-RTX and RTX. In the File rt_event.c you can see a function called "isr_evt_set". This can safely be called from an ISR. You may notice (you will when you follow its path) that it does not actually make any calls to change any events in any tasks. It adds (safely) an entry into the "post service request" "ps-queue". It then sets the PendSV flag for later processing. You should be able to see this. Then you just need to look at the PendSV processing. In the PendSV processing this queue is serviced. All items are popped out of the queue and processed. It should be fairly easy to convince yourself that the PendSV is safe. The only "issue" would be the decrement of the item count in the queue and you should be able to satisfy yourself that this is safe. We have a similar situation on the insertion into the queue.
I certainly understand how this is not obvious that it is fully "ISR-safe". I had to assume that Keil must know what they are doing and tried to figure out how it was safe. It is that all OS function calls ARE made at priority level 0xFF, which you had already figured needed to be the case. It is the safely queuing of os calls to be made and the processing of them in the PendSV that explains how to allow Nested ISRs to actually behave properly. I have not mentioned yet that the post processing queue is of limited size and can overflow. This is really a catastrophic error with no nice way to recover from (you cannot just "try-again" later). The size of this queue is adjustable by the user, and the entries are not large so that helps limit the total space. This used to be hard coded to 16 entries and this really was sufficient for almost all applications, but it is not hard to picture that there could be a case that would require more so they added that flexibility.
In the CMSIS RTX source code(based on cortex-M3), the priority of SVC exception is not 0xFF. It depends on the priority group setting. The systick exception priority is 0x0. The PendSV exception priority is 0xFF. The source code is listed here:
__inline static void rt_svc_init (void) { #if !defined(__TARGET_ARCH_6S_M) U32 sh,prigroup; #endif NVIC_SYS_PRI3 |= 0x00FF0000U; #if defined(__TARGET_ARCH_6S_M) NVIC_SYS_PRI2 |= (NVIC_SYS_PRI3<<(8+1)) & 0xFC000000U; #else sh = 8U - __clz(~((NVIC_SYS_PRI3 << 8) & 0xFF000000U)); prigroup = ((NVIC_AIR_CTRL >> 8) & 0x07U); if (prigroup >= sh) { sh = prigroup + 1U; } NVIC_SYS_PRI2 = ((0xFEFFFFFFU << sh) & 0xFF000000U) | (NVIC_SYS_PRI2 & 0x00FFFFFFU); #endif }