@ vim:ft=arm // This variant of the syscall handler supports armv{7,8}-m{,+nofp}. #include #include #include #include #include #include "exc_return.h" .syntax unified #if RT_ARM_FP /* For floating-point, save and restore lr, which contains the exception return * value, and use the floating-point context bit of this value to decide if the * non-volatile fp context should be saved/restored as well. */ #define exc_return ,lr #define EXC_RETURN_SIZE 4 #define return bx lr #define pushlr str lr, [sp, #-8]! // Push only lr but keep sp 8-byte aligned. #define poplr ldr lr, [sp], 8 #define poppc ldr pc, [sp], 8 #define savefp tst lr, EXC_RETURN_NOFP; it eq; vstmdbeq r1!, {s16-s31} #define loadfp tst lr, EXC_RETURN_NOFP; it eq; vldmiaeq r0!, {s16-s31} #else // !RT_ARM_FP /* With nofp, lr can be clobbered and reloaded from an immediate because the * exception return value for tasks is a constant. */ #define exc_return #define EXC_RETURN_SIZE 0 #define return mov r0, TASK_INITIAL_EXC_RETURN; bx r0 #define pushlr #define poplr #define poppc return #define savefp #define loadfp #endif // RT_ARM_FP #if RT_MPU_TASK_REGIONS_ENABLE #define controltemp r2, #define CONTROL_SIZE 4 #define getcontrol mrs r2, control #define setcontrol msr control, r2 /* Force an exception if the context was just saved past the end of the task's * stack. This may unrecoverable, as the stores have already occurred into a * disallowed area, unless this is specifically set up as a stack guard. */ #define teststack ldrt r4, [r1] .macro mpuconfigure mov32 r1, rt_active_task ldr r1, [r1] adds r1, RT_MPU_TASK_CONFIG_OFFSET mov32 r2, RT_MPU_BASE + RT_MPU_REGION_OFFSET mpuset RT_MPU_TASK_REGION_START_ID, RT_MPU_NUM_TASK_REGIONS dsb .endm .macro mpuset r, n .if \n == 0 .exitm .endif #if RT_ARM_V8M && (RT_MPU_NUM_TASK_REGIONS > 4) .if (\r == RT_MPU_TASK_REGION_START_ID) || ((\r % 4) == 0) movs r3, \r & 0xFC str r3, [r2, -4] .endif #endif .if ((\r % 4) == 0) && (\n >= 4) ldmia r1!, {r4-r11} stmia r2, {r4-r11} mpuset (\r + 4), (\n - 4) .elseif ((\r % 4) == 1) && (\n >= 3) ldmia r1!, {r4-r9} #if RT_ARM_V8M add r3, r2, 4 stmia r3, {r4-r9} #else // !RT_ARM_V8M stmia r2, {r4-r9} #endif // RT_ARM_V8M mpuset (\r + 3), (\n - 3) .elseif ((\r % 4) == 2) && (\n >= 2) ldmia r1!, {r4-r7} #if RT_ARM_V8M add r3, r2, 8 stmia r3, {r4-r7} #else // !RT_ARM_V8M stmia r2, {r4-r7} #endif // RT_ARM_V8M mpuset (\r + 2), (\n - 2) .else ldmia r1!, {r4-r5} #if RT_ARM_V8M strd r4, r5, [r2, (\r % 4) * 4] #else // !RT_ARM_V8M strd r4, r5, [r2] #endif // RT_ARM_V8M mpuset (\r + 1), (\n - 1) .endif .endm #else // !RT_MPU_TASK_REGIONS_ENABLE #define controltemp #define CONTROL_SIZE 0 #define getcontrol #define setcontrol #define mpuconfigure #define teststack #endif // RT_MPU_TASK_REGIONS_ENABLE #if RT_ARM_V8M #define psplimtemp r3, #define PSPLIM_SIZE 4 #define getpsplim mrs r3, psplim #define setpsplim msr psplim, r3 #else // !RT_ARM_V8M #define psplimtemp #define PSPLIM_SIZE 0 #define getpsplim #define setpsplim #endif // RT_ARM_V8M #define saveregs stmdb r1!, {controltemp psplimtemp r4-r11 exc_return} #define loadregs ldmia r0!, {controltemp psplimtemp r4-r11 exc_return} #define SR_SIZE (CONTROL_SIZE + PSPLIM_SIZE + EXC_RETURN_SIZE) #define NV_FP_SIZE (16 * 4) // s16-s31 #define NV_INT_SIZE (8 * 4) // r4-r11 #define FP_STACK_PROBE_OFFSET (NV_INT_SIZE + NV_FP_SIZE + SR_SIZE) #define NOFP_STACK_PROBE_OFFSET (NV_INT_SIZE + SR_SIZE) .macro swapcontext // Write the suspending context to its stack pointer. mrs r1, psp #if RT_MPU_TASK_REGIONS_ENABLE #if RT_ARM_FP /* We need to probe the task's stack with unprivileged permissions before * saving the rest of the context, but where to probe depends on whether * the task has a floating-point context. str/strt only allows positive * offsets, so first subtract from the suspending task's stack pointer to * get the address that needs to be probed if the floating-point context is * active. Then, check whether it is active, and either probe that address * and save fp registers, or probe the higher address for when no * floating-point context is active, which is calculated from the same base * register and an offset equal to the difference between the two * addresses, which will be positive. */ sub r2, r1, FP_STACK_PROBE_OFFSET tst lr, EXC_RETURN_NOFP itte eq strteq r1, [r2] vstmdbeq r1!, {s16-s31} strtne r1, [r2, FP_STACK_PROBE_OFFSET - NOFP_STACK_PROBE_OFFSET] #else // !RT_ARM_FP sub r2, r1, NOFP_STACK_PROBE_OFFSET strt r1, [r2] #endif // RT_ARM_FP #else // !RT_MPU_TASK_REGIONS_ENABLE savefp #endif // RT_MPU_TASK_REGIONS_ENABLE getcontrol getpsplim saveregs // Store the new stack pointer with the saved context. mov32 r2, rt_context_prev ldr r2, [r2] str r1, [r2] mpuconfigure // Load the new context from r0. loadregs loadfp // Set the new stack pointer. msr psp, r0 setcontrol setpsplim .endm .section .text.rt_svcall_handler, "ax", %progbits .global rt_svcall_handler .type rt_svcall_handler, %function rt_svcall_handler: mrs r0, psp ldmia r0, {r0-r3} pushlr bl rt_syscall_run cbnz r0, 0f poppc 0: poplr swapcontext return .size rt_svcall_handler, .-rt_svcall_handler .section .text.rt_pendsv_handler, "ax", %progbits .global rt_pendsv_handler .type rt_pendsv_handler, %function rt_pendsv_handler: pushlr bl rt_syscall_run_pending cbnz r0, 0f poppc 0: poplr swapcontext return .size rt_pendsv_handler, .-rt_pendsv_handler .section .text.rt_start, "ax", %progbits .global rt_start .type rt_start, %function rt_start: bl rt_start_context mpuconfigure #if RT_ARM_V8M ldr psplimtemp [r0, CONTROL_SIZE] setpsplim #endif // RT_ARM_V8M // Skip past control, psplim, non-volatile registers, and exc_return. adds r0, 32 + CONTROL_SIZE + PSPLIM_SIZE + EXC_RETURN_SIZE msr psp, r0 /* Set the SPSEL bit in control to switch to the process stack pointer. * NOTE: the first task's nPRIV bit is ignored, but it will be zero because * it hasn't started executing yet. */ movs r0, CONTROL_SPSEL msr control, r0 isb /* sp now points at the beginning of: {r0-r3, r12, lr, pc, psr}. * Load the arguments to rt_task_entry and skip r2-r3, r12, lr, pc, psr. * NOTE: the first task's psr is ignored, but using the current psr is * valid because the task hasn't started executing yet and the ABI * specifies that all apsr flags are undefined on entry and return from * public interfaces. */ ldrd r0, r1, [sp], 32 cpsie i // Fall through to rt_task_entry in the same section. .size rt_start, .-rt_start .global rt_task_entry .type rt_task_entry, %function rt_task_entry: blx r1 b rt_task_exit .size rt_task_entry, .-rt_task_entry