Skip to content

Commit 30283b5

Browse files
kar-rahul-awsJacob Carver
and
Jacob Carver
authored
Fix xTaskNotifyWait & ulTaskNotifyTake determinism. (#833)
This PR fixes the bug described in the following issue: #612. This was originally contributed in the following PR: #625. The implementation suspends the scheduler before exiting the critical section (i.e. before enabling interrupts). If we do not do so, a notification sent from an ISR, which happens after exiting the critical section and before suspending the scheduler, will get lost. The sequence of events will be: 1. Exit critical section. 2. Interrupt - ISR calls xTaskNotifyFromISR which adds the task to the Ready list. 3. Suspend scheduler. 4. prvAddCurrentTaskToDelayedList moves the task to the delayed or suspended list. 5. Resume scheduler does not touch the task (because it is not on the pendingReady list), effectively losing the notification from the ISR. The same does not happen when we suspend the scheduler before exiting the critical section. The sequence of events in this case will be: 1. Suspend scheduler. 2. Exit critical section. 3. Interrupt - ISR calls xTaskNotifyFromISR which adds the task to the pendingReady list as the scheduler is suspended. 4. prvAddCurrentTaskToDelayedList adds the task to delayed or suspended list. Note that this operation does not nullify the add to pendingReady list done in the above step because a different list item, namely xEventListItem, is used for adding the task to the pendingReady list. In other words, the task still remains on the pendingReady list. 5. Resume scheduler moves the task from pendingReady list to the Ready list. ------------ Co-authored-by: Jacob Carver <[email protected]>
1 parent 7ffc6a7 commit 30283b5

File tree

1 file changed

+116
-31
lines changed

1 file changed

+116
-31
lines changed

tasks.c

+116-31
Original file line numberDiff line numberDiff line change
@@ -5198,7 +5198,7 @@ void vTaskPlaceOnEventList( List_t * const pxEventList,
51985198

51995199
configASSERT( pxEventList );
52005200

5201-
/* THIS FUNCTION MUST BE CALLED WITH EITHER INTERRUPTS DISABLED OR THE
5201+
/* THIS FUNCTION MUST BE CALLED WITH THE
52025202
* SCHEDULER SUSPENDED AND THE QUEUE BEING ACCESSED LOCKED. */
52035203

52045204
/* Place the event list item of the TCB in the appropriate event list.
@@ -7480,28 +7480,68 @@ TickType_t uxTaskResetEventItemValue( void )
74807480
TickType_t xTicksToWait )
74817481
{
74827482
uint32_t ulReturn;
7483+
BaseType_t xAlreadyYielded;
74837484

74847485
traceENTER_ulTaskGenericNotifyTake( uxIndexToWaitOn, xClearCountOnExit, xTicksToWait );
74857486

74867487
configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );
74877488

74887489
taskENTER_CRITICAL();
7490+
7491+
/* Only block if the notification count is not already non-zero. */
7492+
if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] == 0UL )
74897493
{
7490-
/* Only block if the notification count is not already non-zero. */
7491-
if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] == 0UL )
7494+
/* Mark this task as waiting for a notification. */
7495+
pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;
7496+
7497+
if( xTicksToWait > ( TickType_t ) 0 )
74927498
{
7493-
/* Mark this task as waiting for a notification. */
7494-
pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;
7499+
traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWaitOn );
74957500

7496-
if( xTicksToWait > ( TickType_t ) 0 )
7501+
/* We MUST suspend the scheduler before exiting the critical
7502+
* section (i.e. before enabling interrupts).
7503+
*
7504+
* If we do not do so, a notification sent from an ISR, which
7505+
* happens after exiting the critical section and before
7506+
* suspending the scheduler, will get lost. The sequence of
7507+
* events will be:
7508+
* 1. Exit critical section.
7509+
* 2. Interrupt - ISR calls xTaskNotifyFromISR which adds the
7510+
* task to the Ready list.
7511+
* 3. Suspend scheduler.
7512+
* 4. prvAddCurrentTaskToDelayedList moves the task to the
7513+
* delayed or suspended list.
7514+
* 5. Resume scheduler does not touch the task (because it is
7515+
* not on the pendingReady list), effectively losing the
7516+
* notification from the ISR.
7517+
*
7518+
* The same does not happen when we suspend the scheduler before
7519+
* exiting the critical section. The sequence of events in this
7520+
* case will be:
7521+
* 1. Suspend scheduler.
7522+
* 2. Exit critical section.
7523+
* 3. Interrupt - ISR calls xTaskNotifyFromISR which adds the
7524+
* task to the pendingReady list as the scheduler is
7525+
* suspended.
7526+
* 4. prvAddCurrentTaskToDelayedList adds the task to delayed or
7527+
* suspended list. Note that this operation does not nullify
7528+
* the add to pendingReady list done in the above step because
7529+
* a different list item, namely xEventListItem, is used for
7530+
* adding the task to the pendingReady list. In other words,
7531+
* the task still remains on the pendingReady list.
7532+
* 5. Resume scheduler moves the task from pendingReady list to
7533+
* the Ready list.
7534+
*/
7535+
vTaskSuspendAll();
74977536
{
7537+
taskEXIT_CRITICAL();
7538+
74987539
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
7499-
traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWaitOn );
7540+
}
7541+
xAlreadyYielded = xTaskResumeAll();
75007542

7501-
/* All ports are written to allow a yield in a critical
7502-
* section (some will yield immediately, others wait until the
7503-
* critical section exits) - but it is not something that
7504-
* application code should ever do. */
7543+
if( xAlreadyYielded == pdFALSE )
7544+
{
75057545
taskYIELD_WITHIN_API();
75067546
}
75077547
else
@@ -7511,10 +7551,13 @@ TickType_t uxTaskResetEventItemValue( void )
75117551
}
75127552
else
75137553
{
7514-
mtCOVERAGE_TEST_MARKER();
7554+
taskEXIT_CRITICAL();
75157555
}
75167556
}
7517-
taskEXIT_CRITICAL();
7557+
else
7558+
{
7559+
taskEXIT_CRITICAL();
7560+
}
75187561

75197562
taskENTER_CRITICAL();
75207563
{
@@ -7557,34 +7600,73 @@ TickType_t uxTaskResetEventItemValue( void )
75577600
uint32_t * pulNotificationValue,
75587601
TickType_t xTicksToWait )
75597602
{
7560-
BaseType_t xReturn;
7603+
BaseType_t xReturn, xAlreadyYielded;
75617604

75627605
traceENTER_xTaskGenericNotifyWait( uxIndexToWaitOn, ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait );
75637606

75647607
configASSERT( uxIndexToWaitOn < configTASK_NOTIFICATION_ARRAY_ENTRIES );
75657608

75667609
taskENTER_CRITICAL();
7610+
7611+
/* Only block if a notification is not already pending. */
7612+
if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED )
75677613
{
7568-
/* Only block if a notification is not already pending. */
7569-
if( pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] != taskNOTIFICATION_RECEIVED )
7570-
{
7571-
/* Clear bits in the task's notification value as bits may get
7572-
* set by the notifying task or interrupt. This can be used to
7573-
* clear the value to zero. */
7574-
pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnEntry;
7614+
/* Clear bits in the task's notification value as bits may get
7615+
* set by the notifying task or interrupt. This can be used to
7616+
* clear the value to zero. */
7617+
pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ] &= ~ulBitsToClearOnEntry;
75757618

7576-
/* Mark this task as waiting for a notification. */
7577-
pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;
7619+
/* Mark this task as waiting for a notification. */
7620+
pxCurrentTCB->ucNotifyState[ uxIndexToWaitOn ] = taskWAITING_NOTIFICATION;
75787621

7579-
if( xTicksToWait > ( TickType_t ) 0 )
7622+
if( xTicksToWait > ( TickType_t ) 0 )
7623+
{
7624+
traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWaitOn );
7625+
7626+
/* We MUST suspend the scheduler before exiting the critical
7627+
* section (i.e. before enabling interrupts).
7628+
*
7629+
* If we do not do so, a notification sent from an ISR, which
7630+
* happens after exiting the critical section and before
7631+
* suspending the scheduler, will get lost. The sequence of
7632+
* events will be:
7633+
* 1. Exit critical section.
7634+
* 2. Interrupt - ISR calls xTaskNotifyFromISR which adds the
7635+
* task to the Ready list.
7636+
* 3. Suspend scheduler.
7637+
* 4. prvAddCurrentTaskToDelayedList moves the task to the
7638+
* delayed or suspended list.
7639+
* 5. Resume scheduler does not touch the task (because it is
7640+
* not on the pendingReady list), effectively losing the
7641+
* notification from the ISR.
7642+
*
7643+
* The same does not happen when we suspend the scheduler before
7644+
* exiting the critical section. The sequence of events in this
7645+
* case will be:
7646+
* 1. Suspend scheduler.
7647+
* 2. Exit critical section.
7648+
* 3. Interrupt - ISR calls xTaskNotifyFromISR which adds the
7649+
* task to the pendingReady list as the scheduler is
7650+
* suspended.
7651+
* 4. prvAddCurrentTaskToDelayedList adds the task to delayed or
7652+
* suspended list. Note that this operation does not nullify
7653+
* the add to pendingReady list done in the above step because
7654+
* a different list item, namely xEventListItem, is used for
7655+
* adding the task to the pendingReady list. In other words,
7656+
* the task still remains on the pendingReady list.
7657+
* 5. Resume scheduler moves the task from pendingReady list to
7658+
* the Ready list.
7659+
*/
7660+
vTaskSuspendAll();
75807661
{
7662+
taskEXIT_CRITICAL();
7663+
75817664
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
7582-
traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWaitOn );
7665+
}
7666+
xAlreadyYielded = xTaskResumeAll();
75837667

7584-
/* All ports are written to allow a yield in a critical
7585-
* section (some will yield immediately, others wait until the
7586-
* critical section exits) - but it is not something that
7587-
* application code should ever do. */
7668+
if( xAlreadyYielded == pdFALSE )
7669+
{
75887670
taskYIELD_WITHIN_API();
75897671
}
75907672
else
@@ -7594,10 +7676,13 @@ TickType_t uxTaskResetEventItemValue( void )
75947676
}
75957677
else
75967678
{
7597-
mtCOVERAGE_TEST_MARKER();
7679+
taskEXIT_CRITICAL();
75987680
}
75997681
}
7600-
taskEXIT_CRITICAL();
7682+
else
7683+
{
7684+
taskEXIT_CRITICAL();
7685+
}
76017686

76027687
taskENTER_CRITICAL();
76037688
{

0 commit comments

Comments
 (0)