Preemption in ISR

csanikita92 wrote on Friday, July 06, 2018:

Hi,

I am working on data race detection and static analysis on FreeRTOS. I had a question regarding how ISRs work in FreeRTOS.

Can a higher priority ISR preempt a lower priority running ISR?

This question arises because the way the fromISR API s are implemented, if a higher priority ISR can preempt a lower priority running ISR, then, the state of the data structures can become corrupted.
Is this observation correct?

Thanks,
Nikita

rtel wrote on Friday, July 06, 2018:

Which port are you using? Some support interrupt nesting
(Cortex-M3/4/7, RX, etc.) some don’t (MSP430, etc.).

csanikita92 wrote on Friday, July 06, 2018:

Thanks for the response.
We are analysing the kernel library itself and not any applications.

So, if the port supports interrupt nesting, then, how is the state of kernel data structures maintained to be consistent when accessed inside ISR?
From what we observed, if a higher priority ISR preempts a lower priority ISR, then the state of the kernel data structures could become corrupted. Is this observation correct or not?

rtel wrote on Friday, July 06, 2018:

A couple of ways - all of which can be seen in the code.

First and most basically there are interrupt safe critical sections that
mask interrupts up to a maximum system call priority level. Then there
are object locks whereby communication objects can be accessed, but any
context switches result from accessing the object are held pending until
the object is unlocked. There is also a scheduler locking mechanism
where task lists are not updated directly, but a pending list is used
temporarily until such time that the scheduler is unlocked.

csanikita92 wrote on Sunday, July 08, 2018:

Thanks for the reply. I understood the synchronization mechanisms you explained. But here is a situation I’m still unclear about.

While going through the FreeRTOSv10.0.0 code, I encountered the following two fromISR API functions: xQueueReceiveFromISR and uxQueueMessagesWaitingFromISR, as defined below

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken ) //code snippet
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
const int8_t cRxLock = pxQueue->cRxLock;
prvCopyDataFromQueue( pxQueue, pvBuffer );
//(PROBLEMATIC ACCESS)
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
//some code here
}
else{
//some code here
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}

and

UBaseType_t uxQueueMessagesWaitingFromISR( const QueueHandle_t xQueue ) //code snippet
{
UBaseType_t uxReturn;
configASSERT( xQueue );
uxReturn = ( ( Queue_t * ) xQueue )->uxMessagesWaiting; // (PROBLEMATIC ACCESS)
return uxReturn;
}

Now, consider the following application code snippet taken from FreeRTOS/Demo/CORTEX_LPC1768_GCC_Rowley/LPCUSB/ USB_CDC.c :

static void BulkIn(unsigned char bEP, unsigned char bEPStatus)
{
long lHigherPriorityTaskWoken = pdFALSE;
// some code here
if (uxQueueMessagesWaitingFromISR( … ) == 0) { //CALL 1
// some code here
return;
}
if( xQueueReceiveFromISR( … ) //CALL 2
{
break;
}
//some code here
portEND_SWITCHING_ISR( lHigherPriorityTaskWoken );
}

Here, suppose 2 tasks are created which call the method BulkIn, then, there can be an instance of uxQueueMessagesWaitingFromISR (Call 1) and xQueueReceiveFromISR (Call 2) running simultaneously. In this case, there is a conflicting access to uxMessagesWaiting.
We realize that xQueueReceiveFromISR calls portSET_INTERRUPT_MASK_FROM_ISR() to prevent interrupt nesting but uxQueueMessagesWaitingFromISR does not. It can happen that while uxQueueMessagesWaitingFromISR is being executed, a call to xQueueReceiveFromISR comes
which results in conflicting simultaneous accesses to uxMessagesWaiting.
Hence, the value of uxMessagesWaiting might become inconsistent.

Please verify whether my observation is correct or not?

Thanks,
Nikita

richard_damon wrote on Sunday, July 08, 2018:

Reading uxMessagesWaiting is atomic, so it will always get a consistant value (so if another ISR changes the queue, it might get the value before or after the other ISR acts) but it will be consistant. uxQueueMessagesWaitingFromISR() adding a critical section inside itself wouldn’t help here, as the other interrupt might occur before or after the critical section. If the ISR calling uxQueueMessagesWaitingFromISR() needs to query the ISR and then act on that, it needs to perform the critical section itself.

xQueueReceiveFromISR() has such a critical section started with the portSET_INTERRUPT_MASK_FROM_ISR() statement to the portCLEAR_INTERRUPT_MASK_FROM_ISR() so that the uxMessagesWaiting and data are always consistant if another ISR tries to get in at the same time.

I don’t know the USB code well enough to know what assumptions the code is making, so I can’t say why it doesn’t need a critical section (assuming there isn’t a SET/CLEAR_INTERRUPT_MASK_FROM_ISR in the dropped sections, but it likely is that other things make sure it isn’t needed, like maybe kicking the interrupt if data is added to the queue.