Resuming from deep sleep

I am running FreeRTOS v10.2.1 on a SAMD51 (Cortex M4F) and am attempting to use its deepest sleep mode (hibernate). The difficulty is that waking from hibernate causes a system reset but I want to resume execution from the state before it went to sleep.

My approach so far…

  1. Configure the power management controller to retain the contents of RAM in hibernate mode.
  2. Define a flag that the main task sets when the system should sleep.
  3. Implement traceTASK_SWITCHED_OUT() to check that flag and, if set, clear it and enter hibernate mode. Doing it this way instead of in configPRE_SLEEP_PROCESSING() means that xPortPendSVHandler() has already stacked the task context (as suggested here).
  4. Insert code in Reset_Handler() before all the C startup stuff to check whether the reset was caused by exiting hibernate mode.
  5. If it was, restore the state of some core registers:
    • Set BASEPRI to the value it had before sleeping
    • Set PendSV and SysTick interrupt priorities
    • Enable the FPU
    • Call vPortSetupTimerInterrupt() to reenable SysTick
    • Set the sleep mode back to idle instead of hibernate
    • SVC
  6. Amend vPortSVCHandler() to test LR and pop the floating-point registers if necessary, as happens in the second half of xPortPendSVHandler(). This shouldn’t affect prvPortStartFirstTask() since portINITIAL_EXC_RETURN has bit 15 set.

This almost works. The system goes to sleep, wakes up, and resumes executing the task from before it slept. But at the first PendSV interrupt, taskSELECT_HIGHEST_PRIORITY_TASK() sets pxCurrentTCB to NULL (in listGET_OWNER_OF_NEXT_ENTRY), which of course leads to a HardFault.

I’m not sure whether the problem is that there’s some processor state I’m not restoring or whether I’m violating some assumption of FreeRTOS’s internals. I’ve checked that AIRCR.PRIGROUP remains zero after reset, and all the NVIC interrupt priorities seem to retain their values. The SCB and SysTick registers appear to lose their configuration at reset, so I manually re-enable the FPU and reconfigure SysTick (point 5 above).

Things I’ve tried that didn’t help:

  1. Restoring the MSP to its pre-sleep value. I don’t think there’s anything useful left on the main stack anyway because at reset I get the task pointer from pxCurrentTCB.
  2. Calling vTaskStepTick() after wakeup to indicate that time has passed.
  3. Setting xYieldPending = true at wakeup. Grasping at straws by this point.

Is anybody able to shed any light on why taskSELECT_HIGHEST_PRIORITY_TASK() would be setting pxCurrentTCB to NULL and how to avoid it? I’ve spent many hours poring over assembly and single-stepping gdb to get to this point but now I’m stuck.

vTaskStepTick() assumes low power mode was entered when there were not tasks wanting to execute and that low power mode exited before or at the time the next task needed to execute. I don’t think your method of entering low power mode can guarantee either of those conditions.

How that macro works depends on your configUSE_PORT_OPTIMISED_TASK_SELECTION setting.

One thought in step 5, are you restoring the pre-reset value of the MSP?

Edit: Nevermind - I should have read to the end of your post first :wink:.

Calling vTaskStepTick() was only an (unsuccessful) experiment. My instinct is not to call it so that essentially no time passes while hibernated from the perspective of the kernel. It doesn’t matter to me if the tick count doesn’t change while in hibernate mode – there are no delays etc. over that time.

I’ve tried with configUSE_PORT_OPTIMISED_TASK_SELECTION set to both 0 and 1 and either way in listGET_OWNER_OF_NEXT_ENTRY(), ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; is NULL.

I think my next step is to dig into the contents of pxReadyTasksLists and see why pvOwner is not set. Maybe hibernating in traceTASK_SWITCHED_IN instead of _OUT would make more sense so that a task switch happens after waking instead of resuming the task that was expecting to be switched out? I’ll give it a go.

I figured out my problem – a couple of weeks’ holiday helps :slight_smile:

As I suspected in my last post, hibernating in traceTASK_SWITCHED_IN instead of _OUT gets around the null pvOwner problem. If you hibernate in traceTASK_SWITCHED_OUT then it resumes the same task after waking, but the kernel has just suspended that task. Hibernating after the new task has been switched in ensures the new task is the one that’s resumed after waking.

So I can now successfully resume FreeRTOS after waking from the deepest sleep mode.

Figuring all this out was complicated by the fact that there seems to be some kind of silicon bug in the SAMD51 that wipes the first 6380 bytes of SRAM despite the power manager being configured to retain all of SRAM in hibernate mode. I’ve temporarily worked around that by tweaking the ram region in the linker script to start past that point. I’ll try to report this to Microchip if I can figure out how to do so.