Where to put WFI in FreeRTOS?

zachmetzinger wrote on Monday, April 11, 2016:

which could be perhaps be a miscalculation if the slower clock count does not increment while the chip would normally be asleep (some of the chip specific low power schemes we have that do use 32K clocks do take it into account).

I also suspected that it was due to the SysTick not incrementing in vPortSuppressTicksAndSleep(). The instructions in there execute far faster, and you’d have to execute 96e6/32768 or ~2930 more instructions before the SysTick current value would be anything other than zero upon exit from that function.

zachmetzinger wrote on Wednesday, April 13, 2016:

The device is related to the MAX32620, so that EV Kit should work for duplicating the issue, should you be so inclined.

rtel wrote on Wednesday, April 13, 2016:

This file contains an implementation of vPortSupressTicksAndSleep() that
works with a 32K clock. Compare its implementation to the default
implementation in port.c and see if anything special was done to ensure
operation at the slow input clock speed:

https://sourceforge.net/p/freertos/code/HEAD/tree/trunk/FreeRTOS/Demo/CORTEX_M4F_CEC1302_Keil_GCC/main_low_power/low_power_tick_config.c

rtel wrote on Wednesday, April 13, 2016:

Just to add to that, specifically I point out this file as it is a 32K
DOWN counter, as SysTick is also a down counter.

zachmetzinger wrote on Tuesday, May 03, 2016:

I think we’ve identified the issue, but it merits a closer look. It appears the problem occurs during the following sequence of events:

  1. pxDelayedTaskList has one task on it
  2. The current thread of execution is in portTASK_FUNCTION (the idle task)
  3. vTaskSwitchContext is called
  4. The scheduler is not suspended, so the else case is taken, and this function exits
  5. xTaskIncrementTick fires
  6. The following code executes
    /* It is time to remove the item from the Blocked state. */
    ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
  7. Next loop around this for ( ;; ) the pxDelayedTaskList is empty
  8. xNextTaskUnblockTime = portMAX_DELAY;
  9. xTaskIncrementTick exits
  10. Thread execution resumes within the idle task about here:
    TickType_t xExpectedIdleTime;
    /* It is not desirable to suspend then resume the scheduler on */
  11. Execution enters vPortSuppressTicksAndSleep (ARM_CM4 implementation)
    a. Note that xExpectedIdleTime is portMAX_DELAY at this point
  12. eTaskConfirmSleepModeStatus() is called for a possible eAbortSleep, but returns eReturn
  13. Device attempts to sleep for xMaximumPossibleSuppressedTicks <- INCORRECT

So, we think the reason that the failure occurs is that eTaskConfirmSleepModeStatus() doesn’t check pxReadyTasksLists. Does this seem reasonable?

zachmetzinger wrote on Tuesday, May 03, 2016:

This addition to eTaskConfirmSleepModeStatus appears to fix the issue we were tracking down.

	/* First, check that there are no tasks on the pxReadyTasksLists higher than tskIDLE_PRIORITY */
	for( uxPriority = ( UBaseType_t ) (tskIDLE_PRIORITY+1); uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
	{
	  if (listLIST_IS_EMPTY(&( pxReadyTasksLists[ uxPriority ] )) != pdTRUE) {
	    eReturn = eAbortSleep;
	    break;
	  }
	}
 

rtel wrote on Wednesday, May 04, 2016:

I would need to follow through your scenario in more detail, with the code in front of me, as I’m not following it exactly just reading it in isolation - but some initial comments.

eTaskConfirmSleepModeStatus() is only checking that no tasks have been made ready, and no interrupts have executed, since the scheduler was suspended. If one of these events did occur since the scheduler was suspended then the task could not have been placed into a ready list (because the scheduler was suspended) so the additional test you propose should not be needed.

If there is an obscure race condition you have found somewhere, and the test is needed, then inspecting the highest priority ready state variable (which might be a priority value or a bit mask, depending on the setting of configUSE_PORT_OPTIMISED_TASK_SELECTION) would be a faster way of finding the information rather than looping through all the ready lists - which would not be desirable due to the time it would take to execute.

rtel wrote on Wednesday, May 04, 2016:

More detail:

  1. pxDelayedTaskList has one task on it

Ok.

  1. The current thread of execution is in portTASK_FUNCTION (the idle task)

Ok.

  1. vTaskSwitchContext is called

By what? Is this the idle task calling taskYIELD()?

  1. The scheduler is not suspended, so the else case is taken, and this function exits

…so presumably when it exits it returns to the Idle task, so no context switch was performed. Is that correct?

  1. xTaskIncrementTick fires

Presumably this is the tick interrupt executing and calling this function.

  1. The following code executes
    /* It is time to remove the item from the Blocked state. */
    ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );

Ok.

  1. Next loop around this for ( ;; ) the pxDelayedTaskList is empty

So now there are no tasks in the Blocked state.

  1. xNextTaskUnblockTime = portMAX_DELAY;

Presumably because there are no tasks in the Blocked state. At this point do you just have the Idle task and the task that was unblocked in the tick interrupt executing?

  1. xTaskIncrementTick exits

Ok.

  1. Thread execution resumes within the idle task about here:
    TickType_t xExpectedIdleTime;
    /* It is not desirable to suspend then resume the scheduler on */

If the Idle task is still running is it right that the task that was unblocked by the tick interrupt is also running at the idle priority?

  1. Execution enters vPortSuppressTicksAndSleep (ARM_CM4 implementation)
    a. Note that xExpectedIdleTime is portMAX_DELAY at this point

From your description so far, vPortSuppressTicksAndSleep() should never even be called because prvGetExpectedIdleTime() will return 0 if the the other task is running at the idle priority, see the line…

else if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > 1 )

…or if the task that unblocked has a priority above the idle priority (in which case the idle task should not even be executing):

So it seems this is the part where the problem is.

What do you have the configUSE_PORT_OPTIMISED_TASK_SELECTION and configEXPECTED_IDLE_TIME_BEFORE_SLEEP set to?

zachmetzinger wrote on Wednesday, May 04, 2016:

vTaskSwitchContext is called

By what? Is this the idle task calling taskYIELD()?

Yes, in the (configUSE_PREEMPTION == 0) block, taskYIELD() is being called.

The scheduler is not suspended, so the else case is taken, and this function exits

…so presumably when it exits it returns to the Idle task, so no context switch was performed. Is that correct?

Correct. According to my trace, the next bit of code that runs after vTaskSwitchContext is xTaskIncrementTick, which then resumes execution within the idle task before TickType_t xExpectedIdleTime.

  xNextTaskUnblockTime = portMAX_DELAY;

Presumably because there are no tasks in the Blocked state. At this point do you just have the Idle task and the task that was unblocked in the tick interrupt executing?

Yes, only the idle task and the task just unblocked.

If the Idle task is still running is it right that the task that was unblocked by the tick interrupt is also running at the idle priority?

The “other” task, which is not the idle task, is called ‘Task1’. It has tskIDLE_PRIORITY+1.

…or if the task that unblocked has a priority above the idle priority (in which case the idle task should not even be executing):

Task1 was suspended for 500ms, which expired, and it was put on the pxReadyTasksLists[tskIDLE_PRIORITY+1] list. However, the scheduler did not switch to it, so the idle task carried along and tried to sleep. This only happens when configSYSTICK_CLOCK_HZ is defined – it has the value 32768. If this is not defined and the CPU clock is also SYSTICK clock, this condition does not happen. I believe it to be a race condition exacerbated by the slow SysTick clock.

What do you have the configUSE_PORT_OPTIMISED_TASK_SELECTION and configEXPECTED_IDLE_TIME_BEFORE_SLEEP set to?

I’m using the default GCC/ARM_CM4F/portmacro.h defines.

/* Architecture specific optimisations. */
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
	#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif
#ifndef configEXPECTED_IDLE_TIME_BEFORE_SLEEP
	#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
#endif

rtel wrote on Thursday, May 05, 2016:

Task1 was suspended for 500ms, which expired, and it was put on the
pxReadyTasksLists[tskIDLE_PRIORITY+1] list. However, the scheduler did
not switch to it, so the idle task carried along and tried to sleep

Why did the scheduler not switch to it? If the description is accurate
then that would seem to be the cause of your problem - and the code
afterwards is only doing the wrong thing because the idle task should
not be running anyway.

zachmetzinger wrote on Thursday, May 05, 2016:

Why did the scheduler not switch to it?

Unfortunately, I am not an expert in the internals of FreeRTOS.

I think we have a work-around which we can provide to our customers, and it seems harmless enough if it is a “should never occur” case. We’ll take a deeper look at it when more time is available.