uxTopReadyPriority updated from ISR while switching task

romainht wrote on Thursday, January 25, 2018:

Hello everyone,

we are currently using FreeRTOS on a Blackfin processor and facing an issue when releasing a high-priority task from ISR. We may have made a mistake during porting but are unable to find where. I found several topics linked to this issue (uxTopReadyPriority variable in particular) but still do not understand the problem.

We have the following config :
configUSE_PREEMPTION = 1
configUSE_PORT_OPTIMISED_TASK_SELECTION = 0

We have sereral tasks running at low priority (3) and one task running at higher priority (5).

In the context switching interrupt subroutine (lowest IRQ priority), we call vTaskSwitchContext after enabling interrupts again, thus taskSELECT_HIGHEST_PRIORITY_TASK is called with interrupts on.

What we see
While we are in vTaskSwitchContext, we enter taskSELECT_HIGHEST_PRIORITY_TASK. At that time, the high-priority task is asleep and uxTopReadyPriority=3. uxTopPriority is loaded with this value.

At that point, an IRQ is triggered (priority higher than task switching ISR but lower than configMAX_SYSCALL_INTERRUPT_PRIORITY) and it calls sys_semaphore_releaseISR to release the high-priority task.
sys_semaphore_releaseISR calls xQueueGiveFromISR -> xTaskRemoveFromEventList -> prvAddTaskToReadyList -> taskRECORD_READY_PRIORITY and finally uxTopReadyPriority is set to 5.

When we are back in the context switching subroutine, we are still in taskSELECT_HIGHEST_PRIORITY_TASK and have uxTopPriority=3. We run the “uxTopReadyPriority = uxTopPriority;” command, that sets uxTopReadyPriority to 3 back again. As a result, the high-priority task will never be executed.

Thus, did we made a mistake with our interrupt priorities ? Is it a normal bahaviour that a task can call FromISR functions while the task switching interrupt is in taskSELECT_HIGHEST_PRIORITY_TASK ?

Task switching interrupt Other interrupt
vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK

UBaseTypet uxTopPriority = uxTopReadyPriority;
| sys_semaphore_releaseISR
| xQueueGiveFromISR
| xTaskRemoveFromEventList
|prvAddTaskToReadyList
| taskRECORD_READY_PRIORITY
| uxTopReadyPriority=5
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) |
… |
uxTopReadyPriority = uxTopPriority; |

Thanks for any comment that would help us understand how this should work.
Romain

rtel wrote on Thursday, January 25, 2018:

Hi - first to point out that we don’t provide a Blackfin port ourselves,
so we don’t know how the port is implemented. However…

In the context switching interrupt subroutine (lowest IRQ priority), we > call vTaskSwitchContext after enabling interrupts again, thus >
taskSELECT_HIGHEST_PRIORITY_TASK is called with interrupts on.
…if the port supports interrupt nesting then that sounds wrong.
Interrupts should be masked either completely, or up to
configMAX_SYSCALL_INTERRUPT_PRIORITY when vTaskSwitchContext() is called.

Does the port support interrupt nesting? It sounds like it does if
variables in vTaskSwitchContext() change while it is being executed
(implying it has been re-entered somewhere).

If you only see this when stepping through code in the debugger then
another possibility could be that the debugger is enabling interrupts
between steps.

romainht wrote on Thursday, January 25, 2018:

Thanks for your answer Richard.

Indeed the port supports interrupt nesting. The problem appears not only when using debugger but also when running “normally”, so the debugger is not to blame.

Thus, if I understand well, vTaskSwitchContext has to be called from a critical section if interrupt nesting is enabled ? We may have missed this point…

On the other hand, we tried to have a better understanding by reading the Cortex-a9 port, and vTaskSwitchContext is called from the SWI handler, which can be interrupted by the IRQ handler. So we did not find what prevents FromISR functions calls from an ISR while taskSELECT_HIGHEST_PRIORITY_TASK is running in the SWI handler.

rtel wrote on Thursday, January 25, 2018:

I think, without checking ;o) that the Cortex-A9 calls the ISR with
interrupt disabled, and because they are not re-enabled,
vTaskSwitchContext() executes without the risk of itself being
interrupted. The Cortex-A ports have a single interrupt entry point,
and some people prefer to enable interrupts there, before [manually]
vectoring to the actual interrupting peripheral. Others prefer to leave
interrupts disabled there, and only selectively re-enable interrupts (to
allow nesting) inside the individual interrupt handlers.

On the Cortex-M parts the PendSV interrupt is used for context
switching. If you look at the handler you will see interrupts being
masked up to the configMAX_SYSCALL_INTERRUPT_PRIORITY (not globally
masked) by a write to the basepri register immediately before calling
vTaskSwitchContext(). At the time of writing this can be seen on line
406 on the following link:
https://sourceforge.net/p/freertos/code/HEAD/tree/trunk/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c

romainht wrote on Friday, January 26, 2018:

I read the code from the Cotex-M and it seems clear to me. I understand that there is no conflict when accessing the uxTopReadyPriority variable has low priority interrupts are off when we are in vTaskSwitchContext.

But if I’m right, the Cortex-A9 port we looked at is a bit different and closer to our code. The SWI handler (task switch) is not called from the interrupt entry point but from the SVC entry point, which has a lower priority than IRQs and can be interrupted by them (code is located in portable/RVDS/ARM_CA9).
However, it seems that an optimized version of task selection replaces the default one,
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31 - __clz( uxReadyPriorities ) )

This code does not overwrite the topPriority variable in portGET_HIGHEST_PRIORITY and the release of the high priority task is not masked. Using a aquivalent code for our port seems to solve our issue (without border effects I hope).
Could it be that the default task selection code shall not be used when vTaskSwitchContext may be interrupted, whereas this optimized version can be ?

rtel wrote on Friday, January 26, 2018:

The code you show above is for when
configUSE_PORT_OPTIMISED_TASK_SELECTION is set to 1. If you use that
code you must also set that constant as it changes the way priorities
are tracked from a simple integer to a bitmap (hence the use of the CLZ
instruction, using a single ASM instruction to find the highest priority).

Careful in your comparison with the A9 port as different processors work
in very different ways. I think you will find that SVC disables
interrupts before entering the handler, and if interrupts are not
re-enabled in the handler (again, I’ve not actually checked) then
interrupts remain disabled during the task selection.

romainht wrote on Wednesday, January 31, 2018:

We finally disabled all interrupts before entering vTaskSwitchContext to avoid any problem.
Thanks for your help.