/* * FreeRTOS Kernel V11.1.0 * Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * https://www.FreeRTOS.org * https://github.com/FreeRTOS * */ .arm .syntax unified .section privileged_functions #define FREERTOS_ASSEMBLY #include "portmacro_asm.h" #include "mpu_syscall_numbers.h" #undef FREERTOS_ASSEMBLY /* External FreeRTOS-Kernel variables. */ .extern pxCurrentTCB .extern uxSystemCallImplementations .extern ulPortInterruptNesting .extern ulPortYieldRequired /* External Llnker script variables. */ .extern __syscalls_flash_start__ .extern __syscalls_flash_end__ /* External FreeRTOS-Kernel functions. */ .extern vTaskSwitchContext .extern vApplicationIRQHandler /* ----------------------------------------------------------------------------------- */ /* Save the context of a FreeRTOS Task. */ .macro portSAVE_CONTEXT DSB ISB /* Push R0 and LR to the stack for current mode. */ PUSH { R0, LR } LDR LR, =pxCurrentTCB /* LR = &( pxCurrentTCB ). */ LDR LR, [LR] /* LR = pxCurrentTCB. */ LDR LR, [LR] /* LR = pxTopOfStack i.e. the address where to store the task context. */ LDR R0, =ulCriticalNesting /* R0 = &( ulCriticalNesting ). */ LDR R0, [R0] /* R0 = ulCriticalNesting. */ STM LR!, { R0 } /* Store ulCriticalNesting. ! increments LR after storing. */ #if ( portENABLE_FPU == 1 ) VMRS R0, FPSCR /* R0 = FPSCR. */ STM LR!, { R0 } /* Store FPSCR. */ VSTM LR!, { D0-D15 } /* Store D0-D15. */ #endif /* ( portENABLE_FPU == 1 ) */ POP { R0 } /* Restore R0 to pre-exception value. */ /* STM (user registers) - In a PL1 mode other than System mode, STM (user * registers) instruction stores multiple User mode registers to * consecutive memory locations using an address from a base register. The * processor reads the base register value normally, using the current mode * to determine the correct Banked version of the register. This instruction * cannot writeback to the base register. * * The following can be derived from the above description: * - The macro portSAVE_CONTEXT MUST be called from a PL1 mode other than * the System mode. * - Base register LR of the current mode will be used which contains the * location to store the context. * - It will store R0-R14 of User mode i.e. pre-exception SP(R13) and LR(R14) * will be stored. */ STM LR, { R0-R14 }^ ADD LR, LR, #60 /* R0-R14 - Total 155 register, each 4 byte wide. */ POP { R0 } /* Pre-exception PC is in R0. */ MRS R1, SPSR /* R1 = Pre-exception CPSR. */ STM LR!, { R0-R1 } /* Store pre-exception PC and CPSR. */ .endm /* ----------------------------------------------------------------------------------- */ /* Restore the context of a FreeRTOS Task. */ .macro portRESTORE_CONTEXT /* Load the pointer to the current task's Task Control Block (TCB). */ LDR LR, =pxCurrentTCB /* LR = &( pxCurrentTCB ). */ LDR LR, [LR] /* LR = pxCurrentTCB. */ ADD R1, LR, #0x4 /* R1 now points to the xMPUSettings in TCB. */ LDR LR, [LR] /* LR = pxTopOfStack i.e. the address where to restore the task context from. */ /* When creating a loop label in a macro it has to be a numeric label. * for( R5 = portFIRST_CONFIGURABLE_REGION ; R5 <= portNUM_CONFIGURABLE_REGIONS ; R5++ ) */ MOV R5, #portFIRST_CONFIGURABLE_REGION 123: LDMIA R1!, { R2-R4 } /* R2 = ulRegionSize, R3 = ulRegionAttribute, R4 = ulRegionBaseAddress. */ MCR p15, #0, R5, c6, c2, #0 /* MPU Region Number Register. */ MCR p15, #0, R4, c6, c1, #0 /* MPU Region Base Address Register. */ MCR p15, #0, R3, c6, c1, #4 /* MPU Region Access Control Register. */ MCR p15, #0, R2, c6, c1, #2 /* MPU Region Size and Enable Register. */ ADD R5, R5, #1 CMP R5, #portNUM_CONFIGURABLE_REGIONS BLE 123b LDR R1, =ulCriticalNesting /* R1 = &( ulCriticalNesting ). */ LDM LR!, { R2 } /* R2 = Stored ulCriticalNesting. */ STR R2, [R1] /* Restore ulCriticalNesting. */ #if ( portENABLE_FPU == 1 ) LDM LR!, { R1 } /* R1 = Stored FPSCR. */ VMSR FPSCR, R1 /* Restore FPSCR. */ VLDM LR!, { D0-D15 } /* Restore D0-D15. */ #endif /* portENABLE_FPU*/ /* LDM (User registers) - In a PL1 mode other than System mode, LDM (User * registers) loads multiple User mode registers from consecutive memory * locations using an address from a base register. The registers loaded * cannot include the PC. The processor reads the base register value * normally, using the current mode to determine the correct Banked version * of the register. This instruction cannot writeback to the base register. * * The following can be derived from the above description: * - The macro portRESTORE_CONTEXT MUST be called from a PL1 mode other than * the System mode. * - Base register LR of the current mode will be used which contains the * location to restore the context from. * - It will restore R0-R14 of User mode i.e. SP(R13) and LR(R14) of User * mode will be restored. */ LDM LR, { R0-R14 }^ ADD LR, LR, #60 /* R0-R14 - Total 155 register, each 4 byte wide. */ RFE LR /* Restore PC and CPSR from the context. */ .endm /* ----------------------------------------------------------------------------------- */ /* * void vPortStartFirstTask( void ); */ .align 4 .global vPortStartFirstTask .type vPortStartFirstTask, %function vPortStartFirstTask: /* This function is called from System Mode to start the FreeRTOS-Kernel. * As described in the portRESTORE_CONTEXT macro, portRESTORE_CONTEXT cannot * be called from the System mode. We, therefore, switch to the Supervisor * mode before calling portRESTORE_CONTEXT. */ CPS #SVC_MODE portRESTORE_CONTEXT /* ----------------------------------------------------------------------------------- */ .align 4 .global FreeRTOS_SVC_Handler .type FreeRTOS_SVC_Handler, %function FreeRTOS_SVC_Handler: PUSH { R11-R12 } /* ------------------------- Caller Flash Location Check ------------------------- */ LDR R11, =__syscalls_flash_start__ LDR R12, =__syscalls_flash_end__ CMP LR, R11 /* If SVC instruction address is less than __syscalls_flash_start__, exit. */ BLT svcHandlerExit CMP LR, R12 /* If SVC instruction address is greater than __syscalls_flash_end__, exit. */ BGT svcHandlerExit /* ---------------------------- Get Caller SVC Number ---------------------------- */ MRS R11, SPSR /* LR = CPSR at the time of SVC. */ TST R11, #0x20 /* Check Thumb bit (5) in CPSR. */ LDRHNE R11, [LR, #-0x2] /* If Thumb, load halfword. */ BICNE R11, R11, #0xFF00 /* And extract immidiate field (i.e. SVC number). */ LDREQ R11, [LR, #-0x4] /* If ARM, load word. */ BICEQ R11, R11, #0xFF000000 /* And extract immidiate field (i.e. SVC number). */ /* --------------------------------- SVC Routing --------------------------------- */ /* If SVC Number < #NUM_SYSTEM_CALLS, go to svcSystemCallEnter. */ CMP R11, #NUM_SYSTEM_CALLS BLT svcSystemCallEnter /* If SVC Number == #portSVC_SYSTEM_CALL_EXIT, go to svcSystemCallExit. */ CMP R11, #portSVC_SYSTEM_CALL_EXIT BEQ svcSystemCallExit /* If SVC Number == #portSVC_YIELD, go to svcPortYield. */ CMP R11, #portSVC_YIELD BEQ svcPortYield svcHandlerExit: POP { R11-R12 } MOVS PC, LR /* Copies the SPSR into the CPSR, performing the mode swap. */ svcPortYield: POP { R11-R12 } portSAVE_CONTEXT BL vTaskSwitchContext portRESTORE_CONTEXT svcSystemCallExit: LDR R11, =pxCurrentTCB /* R11 = &( pxCurrentTCB ). */ LDR R11, [R11] /* R11 = pxCurrentTCB. */ ADD R11, R11, #portSYSTEM_CALL_INFO_OFFSET /* R11 now points to xSystemCallStackInfo in TCB. */ /* Restore the user mode SP and LR. */ LDM R11, { R13-R14 }^ AND R12, R12, #0x0 /* R12 = 0. */ STR R12, [R11] /* xSystemCallStackInfo.pulTaskStackPointer = NULL. */ STR R12, [R11, #0x4] /* xSystemCallStackInfo.pulLinkRegisterAtSystemCallEntry = NULL. */ LDMDB R11, { R12 } /* R12 = ulTaskFlags. */ TST R12, #portTASK_IS_PRIVILEGED_FLAG /* If the task is privileged, we can exit now. */ BNE svcHandlerExit /* Otherwise, we need to switch back to User mode. */ MRS R12, SPSR BIC R12, R12, #0x0F MSR SPSR_cxsf, R12 B svcHandlerExit svcSystemCallEnter: LDR R12, =uxSystemCallImplementations /* R12 = uxSystemCallImplementations. */ /* R12 = uxSystemCallImplementations[ R12 + ( R11 << 2 ) ]. * R12 now contains the address of the system call impl function. */ LDR R12, [R12, R11, lsl #2] /* If R12 == NULL, exit. */ CMP R12, #0x0 BEQ svcHandlerExit /* It is okay to clobber LR here because we do not need to return to the * SVC enter location anymore. LR now contains the address of the system * call impl function. */ MOV LR, R12 LDR R11, =pxCurrentTCB /* R11 = &( pxCurrentTCB ). */ LDR R11, [R11] /* R11 = pxCurrentTCB. */ ADD R11, R11, #portSYSTEM_CALL_INFO_OFFSET /* R11 now points to xSystemCallStackInfo in TCB. */ /* Store User mode SP and LR in xSystemCallStackInfo.pulTaskStackPointer and * xSystemCallStackInfo.pulLinkRegisterAtSystemCallEntry. */ STM R11, { R13-R14 }^ ADD R11, R11, 0x8 /* Load User mode SP an LR with xSystemCallStackInfo.pulSystemCallStackPointer * and xSystemCallStackInfo.pulSystemCallExitAddress. */ LDM R11, { R13-R14 }^ /* Change to SYS_MODE for the System Call. */ MRS R12, SPSR ORR R12, R12, #SYS_MODE MSR SPSR_cxsf, R12 B svcHandlerExit /* ----------------------------------------------------------------------------------- */ /* * void vPortDisableInterrupts( void ); */ .align 4 .global vPortDisableInterrupts .type vPortDisableInterrupts, %function vPortDisableInterrupts: CPSID I BX LR /* ----------------------------------------------------------------------------------- */ /* * void vPortEnableInterrupts( void ); */ .align 4 .global vPortEnableInterrupts .type vPortEnableInterrupts, %function vPortEnableInterrupts: CPSIE I BX LR /* ----------------------------------------------------------------------------------- */ /* * void vMPUSetRegion( uint32_t ulRegionNumber, * uint32_t ulBaseAddress, * uint32_t ulRegionSize, * uint32_t ulRegionPermissions ); * * According to the Procedure Call Standard for the ARM Architecture (AAPCS), * paramters are passed in the following registers: * R0 = ulRegionNumber. * R1 = ulBaseAddress. * R2 = ulRegionSize. * R3 = ulRegionPermissions. */ .align 4 .global vMPUSetRegion .type vMPUSetRegion, %function vMPUSetRegion: AND R0, R0, #0x0F /* R0 = R0 & 0x0F. Max possible region number is 15. */ MCR p15, #0, R0, c6, c2, #0 /* MPU Region Number Register. */ MCR p15, #0, R1, c6, c1, #0 /* MPU Region Base Address Register. */ MCR p15, #0, R3, c6, c1, #4 /* MPU Region Access Control Register. */ MCR p15, #0, R2, c6, c1, #2 /* MPU Region Size and Enable Register. */ BX LR /* ----------------------------------------------------------------------------------- */ /* * void vMPUEnable( void ); */ .align 4 .global vMPUEnable .type vMPUEnable, %function vMPUEnable: PUSH { R0 } MRC p15, #0, R0, c1, c0, #0 /* R0 = System Control Register (SCTLR). */ ORR R0, R0, #0x1 /* R0 = R0 | 0x1. Set the M bit in SCTLR. */ DSB MCR p15, #0, R0, c1, c0, #0 /* SCTLR = R0. */ ISB POP { R0 } BX LR /* ----------------------------------------------------------------------------------- */ /* * void vMPUDisable( void ); */ .align 4 .global vMPUDisable .type vMPUDisable, %function vMPUDisable: PUSH { R0 } MRC p15, #0, R0, c1, c0, #0 /* R0 = System Control Register (SCTLR). */ BIC R0, R0, #1 /* R0 = R0 & ~0x1. Clear the M bit in SCTLR. */ /* Wait for all pending data accesses to complete. */ DSB MCR p15, #0, R0, c1, c0, #0 /* SCTLR = R0. */ /* Flush the pipeline and prefetch buffer(s) in the processor to ensure that * all following instructions are fetched from cache or memory. */ ISB POP { R0 } BX LR /* ----------------------------------------------------------------------------------- */ /* * void vMPUEnableBackgroundRegion( void ); */ .align 4 .global vMPUEnableBackgroundRegion .type vMPUEnableBackgroundRegion, %function vMPUEnableBackgroundRegion: PUSH { R0 } MRC p15, #0, R0, c1, c0, #0 /* R0 = System Control Register (SCTLR). */ ORR R0, R0, #0x20000 /* R0 = R0 | 0x20000. Set the BR bit in SCTLR. */ MCR p15, #0, R0, c1, c0, #0 /* SCTLR = R0. */ POP { R0 } BX LR /* ----------------------------------------------------------------------------------- */ /* * void vMPUDisableBackgroundRegion( void ); */ .align 4 .global vMPUDisableBackgroundRegion .type vMPUDisableBackgroundRegion, %function vMPUDisableBackgroundRegion: PUSH { R0 } MRC p15, 0, R0, c1, c0, 0 /* R0 = System Control Register (SCTLR). */ BIC R0, R0, #0x20000 /* R0 = R0 & ~0x20000. Clear the BR bit in SCTLR. */ MCR p15, 0, R0, c1, c0, 0 /* SCTLR = R0. */ POP { R0 } BX LR /* ----------------------------------------------------------------------------------- */ .align 4 .global FreeRTOS_IRQ_Handler .type FreeRTOS_IRQ_Handler, %function FreeRTOS_IRQ_Handler: SUB LR, LR, #4 /* Return to the interrupted instruction. */ SRSDB SP!, #IRQ_MODE /* Save return state (i.e. SPSR_irq and LR_irq) to the IRQ stack. */ /* Change to supervisor mode to allow reentry. It is necessary to ensure * that a BL instruction within the interrupt handler code does not * overwrite LR_irq. */ CPS #SVC_MODE PUSH { R0-R3, R12 } /* Push AAPCS callee saved registers. */ /* Update interrupt nesting count. */ LDR R0, =ulPortInterruptNesting /* R0 = &( ulPortInterruptNesting ). */ LDR R1, [R0] /* R1 = ulPortInterruptNesting. */ ADD R2, R1, #1 /* R2 = R1 + 1. */ STR R2, [R0] /* Store the updated nesting count. */ /* Call the application provided IRQ handler. */ PUSH { R0-R3, LR } BL vApplicationIRQHandler POP { R0-R3, LR } /* Disable IRQs incase vApplicationIRQHandler enabled them for re-entry. */ CPSID I DSB ISB /* Restore the old interrupt nesting count. R0 holds the address of * ulPortInterruptNesting and R1 holds original value of * ulPortInterruptNesting. */ STR R1, [R0] /* Context switch is only performed when interrupt nesting count is 0. */ CMP R1, #0 BNE exit_without_switch /* Check ulPortInterruptNesting to see if the interrupt requested a context * switch. */ LDR R1, =ulPortYieldRequired /* R1 = &( ulPortYieldRequired ). */ LDR R0, [R1] /* R0 = ulPortYieldRequired. */ /* If ulPortYieldRequired != 0, goto switch_before_exit. */ CMP R0, #0 BNE switch_before_exit exit_without_switch: POP { R0-R3, R12 } /* Restore AAPCS callee saved registers. */ CPS #IRQ_MODE RFE SP! switch_before_exit: /* A context switch is to be performed. Clear ulPortYieldRequired. R1 holds * the address of ulPortYieldRequired. */ MOV R0, #0 STR R0, [R1] /* Restore AAPCS callee saved registers, SPSR_irq and LR_irq before saving * the task context. */ POP { R0-R3, R12 } CPS #IRQ_MODE /* The contents of the IRQ stack at this point is the following: * +----------+ * SP+4 | SPSR_irq | * +----------+ * SP | LR_irq | * +----------+ */ LDMIB SP!, { LR } MSR SPSR_cxsf, LR LDMDB SP, { LR } ADD SP, SP, 0x4 portSAVE_CONTEXT /* Call the function that selects the new task to execute. */ BLX vTaskSwitchContext /* Restore the context of, and branch to, the task selected to execute * next. */ portRESTORE_CONTEXT /* ----------------------------------------------------------------------------------- */ .end