Why do we really need to use safe versions of APIs inside interrupts?!

I have a big question about necessity of using From_ISR postfix with APIs that are called within interrupt routines. I checked all the difference between both types of for example xSemaphoreGive() and xSemaphoreGiveFromISR() APIs and i saw that if we comment:
configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
line within vPortValidateInterruptPriority() function inside port.c file for STM32F407 and also comment:
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
line within vPortEnterCritical() inside port.c file, not also we can use both version of these APIs inside any interrupt routine and it works correctly! but we can also use these APIs inside any interrupt routine that has priority higher than configMAX_SYSCALL_INTERRUPT_PRIORITY.

So my big problem is that why then it is emphasized on not to use for example xSemaphoreGive() inside interrupt routine and also not to use xSemaphoreGiveFromISR() inside an interrupt routine with priority higher than configMAX_SYSCALL_INTERRUPT_PRIORITY while i tested as i said and everything worked correctly if we comment those configASSERT() lines

Hi Amin,

to my best understanding, this is for portability reasons (on some platforms, you can not use a unified API) and performance reasons (you don’t want to burn cycles determining at runtime which context you are running under).

Thanks for reply.
But my question was not about why to have two different APIs, my question was about why FreeRTOS use those configASSERT() lines and does not give the permission to use of for example xSemaphoreGive() inside an ISR while without that configASSERT() macro, it will works as correctly as xSemaphoreGiveFromISR().
What is the rational reason for that constraint?

Also the configMAX_SYSCALL_INTERRUPT_PRIORITY limit still applies (to Cortex-M MCUs at least). I think breaking this rule would break critical sections.

Thanks for the reply.
As i said i tried the case and i used a xSmaphoreGiveFromISR() function inside an interrupt routine with priority higher than configMAX_SYSCALL_INTERRUPT_PRIORITY and everything worked correctly if we comment that configASSERT() lines!

Running a more or less simple test a few times often don’t reveal subtle race conditions. Unfortunately :wink:
See also RTOS for ARM Cortex-M and e.g. this post Understanding priority levels of ISR and FreeRTOS APIs - #16 by aggarg (there are more here in the forum) for some more maybe interesting details.

The reason for the asserts is helping to ensure proper use of the API as documented.
That’s the purpose of those contracts. Nothing special.

1 Like

As Hartmut pointed out, you may only think that it works, but it won’t. Doing what you described is wrong and will break under race conditions. Sometimes those conditions will hold true after several days, sometimes weeks of operation.

It may SEEM to work, but the critical sections to guard the update of critical resources won’t block a corruption of the data. A very common source of system crashes is having an ISR running above configMAX_SYSCALL_INTERRUPT_PRIORITY and not using configASSERT to catch it. All it takes is one too high priority interrupt during that critical section to mess up that data structure.

Could you please explain an example with that race condition that things will be messed up by?

A race condition is an issue common to multi-tasking systems.
It’s not specific to FreeRTOS. There are plenty of good resources including examples in the net.

The classic case w/ FreeRTOS would be vListInsert() and vListRemove() which are used everywhere all over the place. They must be “atomic” with respect to the same list because otherwise the list will be left corrupted or incoherent. Thus, if one thread of execution gets interrupted while it has the list unchained but not rechained, and another thread tries to access the list, everything can happen. The crit section is one way to ensure that this does not happen.

If the mutual exclusion does not work reliably, what happens is that there is a certain percentage of interruption sequeneces that will leave the queue coherent, and another percentage that will leave it corrupted. Typically the latter is smaller, so out of x times an uncontrolled access happens, a certain (unpredictable) number of times the list will get corrupted. The other times it’ll remain intact by sheer dumb luck.

1 Like

That was convincing reason, thanks for the time you have spent on.

@hs2 And also i found a similar thing to what @RAc said from the link you have shared: Understanding priority levels of ISR and FreeRTOS APIs - #6 by richard-damon

Best regards

The main reason not to use a non FromISR version is that it may try to block, which is not possible from an ISR. Also it may be that the interrupt nesting depth count will get corrupt as outside of interrupt it is stored in a variable, where in the FromISR versions it is saved on the stack.

Additionally, most ports, unlike the cortex M port, use a service call style assembly routine or wrappers around the interrupts to perform context switches which won’t work with the non FromISR APIs, especially if the interrupt nesting depth in explicitly saved as part of the interrupt context.

Regarding critical sections - as per prior posts - it only appears to work because you have so far been lucky. Again assuming Cortex-M, and not true for all ports, the register level mechanism to mask interrupts is the same from both the standard and ISR API - the only difference is how the nesting is managed.

1 Like

Personally, for the Cortex M port I would prefer a single GiveFromISR call, that checks the interrupt state to determine whether to do a task switch or set the PendSV flag. Some of my functions that need to GiveFromISR can be called either from an ISR or from normal code.