@********************************************************************************************************
@                                               uC/OS-II
@                                         The Real-Time Kernel
@
@                               (c) Copyright 1992-2012, Micrium, Weston, FL
@                                          All Rights Reserved
@
@                                           ARM Cortex-M0 Port
@
@ File      : OS_CPU_A.S
@ Version   : V2.92.07.00
@ By        : JJL
@             BN
@             MD
@
@ For       : ARMv6M Cortex-M0
@ Mode      : Thumb2
@ Toolchain : GNU C Compiler
@             Atollic TrueSTUDIO
@             for ARM V3.1.0 Pro

@********************************************************************************************************

@********************************************************************************************************
@                                           PUBLIC FUNCTIONS
@********************************************************************************************************

    .extern  OSRunning                                           @ External references
    .extern  OSPrioCur
    .extern  OSPrioHighRdy
    .extern  OSTCBCur
    .extern  OSTCBHighRdy
    .extern  OSIntExit
    .extern  OSTaskSwHook
    .extern  OS_CPU_ExceptStkBase


    .global  OS_CPU_SR_Save                                      @ Functions declared in this file
    .global  OS_CPU_SR_Restore
    .global  OSStartHighRdy
    .global  OSCtxSw
    .global  OSIntCtxSw
    .global  OS_CPU_PendSVHandler

@********************************************************************************************************
@                                                EQUATES
@********************************************************************************************************

.equ NVIC_INT_CTRL,        0xE000ED04                           @ Interrupt control state register.
.equ NVIC_SYSPRI14,        0xE000ED20                           @ System priority register (priority 14).
.equ NVIC_PENDSV_PRI,      0x00FF0000                           @ PendSV priority value (lowest).
.equ NVIC_PENDSVSET,       0x10000000                           @ Value to trigger PendSV exception.

@********************************************************************************************************
@                                      CODE GENERATION DIRECTIVES
@********************************************************************************************************

.text
.align 2
.thumb
.syntax unified

@********************************************************************************************************
@                                   CRITICAL SECTION METHOD 3 FUNCTIONS
@
@ Description: Disable/Enable interrupts by preserving the state of interrupts.  Generally speaking you
@              would store the state of the interrupt disable flag in the local variable 'cpu_sr' and then
@              disable interrupts.  'cpu_sr' is allocated in all of uC/OS-II's functions that need to
@              disable interrupts.  You would restore the interrupt disable state by copying back 'cpu_sr'
@              into the CPU's status register.
@
@ Prototypes :     OS_CPU_SR  OS_CPU_SR_Save(void);
@                  void       OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
@
@
@ Note(s)    : 1) These functions are used in general like this:
@
@                 void Task (void *p_arg)
@                 {
@                 #if OS_CRITICAL_METHOD == 3                   /* Allocate storage for CPU status register */
@                     OS_CPU_SR  cpu_sr;
@                 #endif
@
@                          :
@                          :
@                     OS_ENTER_CRITICAL();                      /* cpu_sr = OS_CPU_SaveSR();                */
@                          :
@                          :
@                     OS_EXIT_CRITICAL();                       /* OS_CPU_RestoreSR(cpu_sr);                */
@                          :
@                          :
@                 }
@********************************************************************************************************

.thumb_func
OS_CPU_SR_Save:
    MRS     R0, PRIMASK                                         @ Set prio int mask to mask all (except faults)
    CPSID   I
    BX      LR

.thumb_func
OS_CPU_SR_Restore:
    MSR     PRIMASK, R0
    BX      LR

@********************************************************************************************************
@                                          START MULTITASKING
@                                       void OSStartHighRdy(void)
@
@ Note(s) : 1) This function triggers a PendSV exception (essentially, causes a context switch) to cause
@              the first task to start.
@
@           2) OSStartHighRdy() MUST:
@              a) Setup PendSV exception priority to lowest;
@              b) Set initial PSP to 0, to tell context switcher this is first run;
@              c) Set the main stack to OS_CPU_ExceptStkBase;
@              d) Set OSRunning to TRUE;
@              e) Trigger PendSV exception;
@              f) Enable interrupts (tasks will run with interrupts enabled).
@********************************************************************************************************

.thumb_func
OSStartHighRdy:
    LDR     R0, =NVIC_SYSPRI14                                  @ Set the PendSV exception priority
    LDR     R1, =NVIC_PENDSV_PRI
    STR     R1, [R0]                                            @ Not storing entire byte with STRB to avoid error

    MOVS    R0, #0                                              @ Set the PSP to 0 for initial context switch call
    MSR     PSP, R0

    LDR     R0, =OS_CPU_ExceptStkBase                           @ Initialize the MSP to the OS_CPU_ExceptStkBase
    LDR     R1, [R0]
    MSR     MSP, R1    

    LDR     R0, =OSRunning                                      @ OSRunning = TRUE
    MOVS    R1, #1
    STRB    R1, [R0]

    LDR     R0, =NVIC_INT_CTRL                                  @ Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]

    CPSIE   I                                                   @ Enable interrupts at processor level

OSStartHang:
    B       OSStartHang                                         @ Should never get here


@********************************************************************************************************
@                               PERFORM A CONTEXT SWITCH (From task level)
@                                           void OSCtxSw(void)
@
@ Note(s) : 1) OSCtxSw() is called when OS wants to perform a task context switch.  This function
@              triggers the PendSV exception which is where the real work is done.
@********************************************************************************************************

.thumb_func
OSCtxSw:
    LDR     R0, =NVIC_INT_CTRL                                  @ Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR


@********************************************************************************************************
@                             PERFORM A CONTEXT SWITCH (From interrupt level)
@                                         void OSIntCtxSw(void)
@
@ Notes:    1) OSIntCtxSw() is called by OSIntExit() when it determines a context switch is needed as
@              the result of an interrupt.  This function simply triggers a PendSV exception which will
@              be handled when there are no more interrupts active and interrupts are enabled.
@********************************************************************************************************

.thumb_func
OSIntCtxSw:
    LDR     R0, =NVIC_INT_CTRL                                  @ Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

@********************************************************************************************************
@                                         HANDLE PendSV EXCEPTION
@                                     void OS_CPU_PendSVHandler(void)
@
@ Note(s) : 1) PendSV is used to cause a context switch.  This is a recommended method for performing
@              context switches with Cortex-M0.  This is because the Cortex-M0 auto-saves half of the
@              processor context on any exception, and restores same on return from exception.  So only
@              saving of R4-R7 is required and fixing up the stack pointers.  Using the PendSV exception
@              this way means that context saving and restoring is identical whether it is initiated from
@              a thread or occurs due to an interrupt or exception.
@
@           2) Pseudo-code is:
@              a) Get the process SP, if 0 then skip (goto d) the saving part (first context switch);
@              b) Save remaining regs R4-R7 on process stack;
@              c) Save the process SP in its TCB, OSTCBCur->OSTCBStkPtr = SP;
@              d) Call OSTaskSwHook();
@              e) Get current high priority, OSPrioCur = OSPrioHighRdy;
@              f) Get current ready thread TCB, OSTCBCur = OSTCBHighRdy;
@              g) Get new process SP from TCB, SP = OSTCBHighRdy->OSTCBStkPtr;
@              h) Restore R4-R7 from new process stack;
@              i) Perform exception return which will restore remaining context.
@
@           3) On entry into PendSV handler:
@              a) The following have been saved on the process stack (by processor):
@                 xPSR, PC, LR, R12, R0-R3
@              b) Processor mode is switched to Handler mode (from Thread mode)
@              c) Stack is Main stack (switched from Process stack)
@              d) OSTCBCur      points to the OS_TCB of the task to suspend
@                 OSTCBHighRdy  points to the OS_TCB of the task to resume
@
@           4) Since PendSV is set to lowest priority in the system (by OSStartHighRdy() above), we
@              know that it will only be run when no other exception or interrupt is active, and
@              therefore safe to assume that context being switched out was using the process stack (PSP).
@********************************************************************************************************

.thumb_func
OS_CPU_PendSVHandler:
    CPSID   I                                                   @ Prevent interruption during context switch
    MRS     R0, PSP                                             @ PSP is process stack pointer

    CMP     R0, #0
    BEQ     OS_CPU_PendSVHandler_nosave							@ equivalent code to CBZ from M3 arch to M0 arch
																@ Except that it does not change the condition code flags

    SUBS    R0, R0, #0x10                                       @ Adjust stack pointer to where memory needs to be stored to avoid overwriting
    STM     R0!, {R4-R7}                                        @ Stores 4 4-byte registers, default increments SP after each storing
    SUBS    R0, R0, #0x10                                       @ STM does not automatically call back the SP to initial location so we must do this manually

    LDR     R1, =OSTCBCur                                       @ OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            @ R0 is SP of process being switched out

                                                                @ At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave:
    PUSH   {R14}                                                @ Save LR exc_return value
    LDR     R0, =OSTaskSwHook                                   @ OSTaskSwHook();
    BLX     R0
    POP    {R0}
    MOV     R14, R0

    LDR     R0, =OSPrioCur                                      @ OSPrioCur = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCur                                       @ OSTCBCur  = OSTCBHighRdy;
    LDR     R1, =OSTCBHighRdy
    LDR     R2, [R1]
    STR     R2, [R0]

    LDR     R0, [R2]                                            @ R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;

    LDM     R0!, {R4-R7}                                        @ Restore R4-R7 from new process stack

    MSR     PSP, R0                                             @ Load PSP with new process SP

    MOV     R0,  R14
    MOVS    R1,  #0x04                                          @ Immediate move to register
    ORRS    R0,  R1                                             @ Ensure exception return uses process stack
    MOV     R14, R0
    CPSIE   I
    BX      LR                                                  @ Exception return will restore remaining context


 .end
