//------------------------------------------------------------------------------
// @file hal/micro/cortexm3/context-switch.s79
// @brief Context save/restore for deep sleep using the PendSV exception.
//
// This file also contains a simple halInternalIdleSleep() function that
// executes just the WFI instruction for idle sleeping.
// 
// When the STM32W108XX enters deep sleep, the hardware will actually remove power
// from the Cortex-M3 core (in Deep Sleep 0, power is not removed but the core
// is held in reset).  Since this will clear the internal state of the core, it
// must be properly restored such that execution can resume from the sleep code.
// The simplest and most secure mechanism to do this is to perform a context save
// and restore.  Context save/restore is almost identical to a context switch
// used in multi-threaded systems with the main difference being only one stack
// pointer is used and the save/restore operations are disjoint.
// 
// When an interrupt is triggered in the STM32W108XX, the core automatically saves 8
// of the 16 CPU registers on the stack.  The ISR then only needs to save the
// other 8 registers and store the resulting stack pointer.  Restoring is the
// reverse operation where 8 registers are manually copied back with the other 8
// being restored on the return from interrupt.
// 
// As its last act, the deep sleep code will trigger the PendSV exception to
// perform a context save.  When the core is booted upon deep sleep exit, the
// RESET_EVENT register informs cstartup if the chip just exited deep sleep.
// Cstartup will then trigger halTriggerContextRestore which sets up the stack
// pointer and trigger the PendSV exception to perform a restore.  When PendSV
// returns from interrupt context the system will be back at the same point it
// was before deep sleep.
// 
// 
// <!--(C) COPYRIGHT 2010 STMicroelectronics. All rights reserved.        -->
//------------------------------------------------------------------------------

#include "compiler/asm.h"

//------------------------------------------------------------------------------
//   boolean halPendSvSaveContext
//
// A simple boolean flag used to indicate if a context save or a context restore
// should be performed.  Since context switching is handled with the PendSV
// interrupt, parameters cannot be passed into the ISR and as such this boolean
// flag is used.  If this flag is zero, PendSV should perform a context restore.
// If this flag is non-zero, PendSV should perform a context save.
// Note: The smallest unit of storage is a single byte.
//
// NOTE: This flag must be set before PendSV is triggered!
//------------------------------------------------------------------------------
        __BSS__
        __EXPORT__ halPendSvSaveContext
halPendSvSaveContext:
        __SPACE__ 1


//------------------------------------------------------------------------------
//   uint32_t savedMSP
//
// Private storage to hold the saved stack pointer.  This variable is only used
// in this file and should not be extern'ed.  In our current design we
// do not use real context switching, but only context saving and restoring.
// As such, we only need to keep track of the Main Stack Pointer (MSP). This
// variable is used to hold the MSP between a save and a restore.
//------------------------------------------------------------------------------
        __BSS__
        __EXPORT__ savedMSP
savedMSP:
        __SPACE__ 4


//------------------------------------------------------------------------------
//   void halPendSvIsr(void)
//
// This ISR is installed by cstartup in the vector table for the PendSV
// exception.  The purpose of this ISR is to either save the current context
// and trigger sleeping through the 'WFI' instruction, or restore a
// previous context.  The variable halPendSvSaveContext is used to
// decide if a save or a restore is performed.  Since entering/exiting interrupt
// context automatically saves/restores 8 of the 16 CPU registers on the stack
// we need to manually save the other 8 onto the stack as well.
//
// When a context save is complete, the stack will have been expanded by 16
// words with the current Stack Pointer stored in savedMSP.
//
// When a context restore is complete, the stack will have been shrunk by 16
// words with the old context restored after the return instruction.
//
// NOTE: The IAR default handler name for PendSV, PendSV_Handler, is also
//       instantiated here so it routes to the same code as the St
//       name halPendSvIsr.
//------------------------------------------------------------------------------
        __CODE__
        __THUMB__
        __EXPORT__ PendSV_Handler
        __EXPORT__ halPendSvIsr
PendSV_Handler:
halPendSvIsr:
        LDR  R0, =halPendSvSaveContext  //load the variable's address
        LDRB R0, [R0]           //get the value in the variable
        CBZ  R0, contextRestore //if variable is zero, branch to contextRestore
contextSave:
        MRS  R0, MSP          //load the main stack pointer into R0
        SUB  R0, R0, #0x20    //make room on the stack for 8 words (32 bytes)
        MSR  MSP, R0          //load new MSP from adjusted stack pointer
        STM  R0, {R4-R11}     //store R4-R11 (8 words) onto the stack
        LDR  R1, =savedMSP    //load address of savedMSP into R1
        STR  R0, [R1]         //store the MSP into savedMSP
        WFI                   //all saved, trigger deep sleep
        // Even if we fall through the WFI instruction, we will immediately
        // execute a context restore and end up where we left off with no
        // ill effects.  Normally at this point the core will either be
        // powered off or reset (depending on the deep sleep level).
contextRestore:
        LDR  R0, =savedMSP    //load address of savedMSP into R0
        LDR  R0, [R0]         //load the MSP from savedMSP
        LDM  R0, {R4-R11}     //load R4-R11 (8 words) from the stack
        ADD  R0, R0, #0x20    //eliminate the 8 words (32 bytes) from the stack
        MSR  MSP, R0          //restore the MSP from R0
        BX   LR               //return to the old context
        
        
//------------------------------------------------------------------------------
//   void halTriggerContextRestore(void)
//
// Cstartup is responsible for triggering a context restore based upon the
// RESET_EVENT register.  Since the stack pointer sits at the top of memory
// after the core boots, cstartup cannot simply trigger a PendSV to restore
// context as this will cause existing stack data to be over written.  Cstartup
// disables interrupts, pends PendSV, and then calls this function. This
// function simply configures the Stack Pointer to be past the previous data
// such that when interrupts are enabled and PendSV fires it wont corrupt
// previous data.
//------------------------------------------------------------------------------
        __CODE__
        __THUMB__
        __EXPORT__ halTriggerContextRestore
halTriggerContextRestore:
        LDR   R0, =savedMSP    //load address of savedMSP into R0
        LDR   R0, [R0]         //load the MSP from savedMSP
        MSR   MSP, R0          //restore the MSP from R0
        CPSIE i                //enable interrupts and let PendSV fire
        BX    LR               //this return should never be triggered
        
        
//------------------------------------------------------------------------------
//   void halInternalIdleSleep(void)
//
// A simple internal function call (to be called from halSleep) for executing
// the WFI instruction and entering the simple, idle sleep state.
//------------------------------------------------------------------------------
        __CODE__
        __THUMB__
        __EXPORT__ halInternalIdleSleep
halInternalIdleSleep:
        WFI                    //trigger idle sleep
        BX   LR                //return
        
        __END__