@ vim:ft=arm /* The interrupt management code for the syscall handler depends on both the * type of interrupt controller and the source of the syscall interrupt, so we * provide a way to extend this code with assembly macros. * vic_syscall_start needs to mask and clear the syscall interrupt such that * another syscall cannot occur when IRQs are re-enabled. vic_syscall_finish * must unmask the syscall interrupt. Both are run with IRQs disabled. */ #include #include #include #include #include #include .syntax unified #ifdef __thumb__ .thumb #else // arm .arm #endif // __thumb__ #if RT_ARM_FP #define FPEXC_SIZE 4 #if RT_ARM_FP_D32 // fpscr + 32 dp registers #define FP_REGS_SIZE (4 + (32 * 8)) #define pushfp vpush {d16-d31}; vpush {d0-d15} #define popfp vpop {d0-d15}; vpop {d16-d31} #else // !RT_ARM_FP_D32 // fpscr + 16 dp registers #define FP_REGS_SIZE (4 + (16 * 8)) #define pushfp vpush {d0-d15} #define popfp vpop {d0-d15} #endif // RT_ARM_FP_D32 #else // !RT_ARM_FP #define FPEXC_SIZE 0 #endif // RT_ARM_FP #define NV_INT_REGS_SIZE (8 * 4) // r4-r11 #define V_INT_REGS_SIZE (8 * 4) // r0-r3, r12, lr, cpsr, pc #define FP_STACK_PROBE_OFFSET (NV_INT_REGS_SIZE + FPEXC_SIZE + FP_REGS_SIZE) #define NOFP_STACK_PROBE_OFFSET (NV_INT_REGS_SIZE + FPEXC_SIZE) .macro mpuconfigure #if RT_MPU_TASK_REGIONS_ENABLE mov32 r1, rt_active_task ldr r1, [r1] adds r1, RT_MPU_TASK_CONFIG_OFFSET movs r2, RT_MPU_TASK_REGION_START_ID adds r6, r2, RT_MPU_NUM_TASK_REGIONS .Lmpusetloop\@: #if __ARM_ARCH == 7 ldmia r1!, {r3, r4, r5} rgnr_set r2 // region number drbar_set r3 // base address drsr_set r4 // size and enable dracr_set r5 // access control #elif __ARM_ARCH == 8 ldmia r1!, {r3, r4} prselr_set r2 // region number prbar_set r3 // base address prlar_set r4 // limit address #endif // __ARM_ARCH add r2, 1 cmp r2, r6 bne .Lmpusetloop\@ dsb #endif // RT_MPU_TASK_REGIONS_ENABLE .endm .macro swapcontext #if RT_ARM_FP // Check if the task has a floating-point context. vmrs r1, fpexc tst r1, RT_FPEXC_EN #if RT_MPU_TASK_REGIONS_ENABLE /* Before saving more context to the stack, attempt to write to it with * user-mode permissions to ensure that the active task is allowed to. */ ite ne subne r12, sp, FP_STACK_PROBE_OFFSET subeq r12, sp, NOFP_STACK_PROBE_OFFSET strt r1, [r12] #endif // RT_MPU_TASK_REGIONS_ENABLE beq .Lskip_fp_save\@ pushfp vmrs r2, fpscr push {r2} .Lskip_fp_save\@: push {r1, r4-r11} // fpexc, callee-saved registers #else // !RT_ARM_FP #if RT_MPU_TASK_REGIONS_ENABLE sub r12, sp, NOFP_STACK_PROBE_OFFSET strt r4, [r12] #endif // RT_MPU_TASK_REGIONS_ENABLE push {r4-r11} #endif // RT_ARM_FP // Store the stack pointer with the saved context. mov32 r2, rt_context_prev ldr r2, [r2] str sp, [r2] mpuconfigure // Switch to the new task stack. mov sp, r0 #if RT_ARM_FP pop {r1, r4-r11} vmsr fpexc, r1 tst r1, RT_FPEXC_EN beq .Lskip_fp_restore\@ pop {r2} vmsr fpscr, r2 popfp .Lskip_fp_restore\@: #else // !RT_ARM_FP pop {r4-r11} #endif // RT_ARM_FP .endm #include .section .text.rt_svc_handler, "ax", %progbits .global rt_svc_handler .type rt_svc_handler, %function rt_svc_handler: #if RT_MPU_TASK_REGIONS_ENABLE cps MODE_SYS sub r12, sp, 8 strt r0, [r12] cps MODE_SVC #endif // RT_MPU_TASK_REGIONS_ENABLE srsdb sp!, MODE_SYS /* The task passed syscall arguments in r0-r3 and expects them to be * clobbered, along with r12 and lr, so none of them need to be saved, and * r12 and lr can be used in vic_svc_start. */ vic_svc_start cpsie i bl rt_syscall_run cbnz r0, 0f /* If no context switch is required, we can resume the current task without * restoring volatile registers. */ cpsid i, MODE_SYS vic_svc_finish rfeia sp! 0: cps MODE_SYS // Reserve space for volatile registers, but no need to save them. sub sp, 24 /* Holding an exclusive monitor across a system call is technically * possible, but rt_syscall's svc inline assembly has a memory clobber on * it, so this shouldn't happen in real code. Therefore, don't bother * clearing the reservation. */ swapcontext cpsid i vic_svc_finish pop {r0-r3, r12, lr} rfeia sp! .size rt_svc_handler, .-rt_svc_handler .macro syscall_irq_return cpsid i vic_syscall_irq_finish pop {r0-r3, r12, lr} rfeia sp! .endm .section .text.rt_syscall_irq_handler, "ax", %progbits .global rt_syscall_irq_handler .type rt_syscall_irq_handler, %function .balign 4 rt_syscall_irq_handler: #if RT_MPU_TASK_REGIONS_ENABLE cps MODE_SYS sub sp, V_INT_REGS_SIZE #ifdef __thumb__ strt r0, [sp] add sp, V_INT_REGS_SIZE #else // arm strt r0, [sp], V_INT_REGS_SIZE #endif // __thumb__ cps MODE_IRQ #endif // RT_MPU_TASK_REGIONS_ENABLE sub lr, 4 srsdb sp!, MODE_SYS cps MODE_SYS push {r0-r3, r12, lr} vic_syscall_irq_start cpsie i, MODE_SVC bl rt_syscall_run_pending cps MODE_SYS cbnz r0, 0f syscall_irq_return /* rt_syscall_run_pending always uses an atomic instruction, so the * exclusive reservation will already be cleared at this point. */ 0: swapcontext syscall_irq_return .size rt_syscall_irq_handler, .-rt_syscall_irq_handler .section .text.rt_start, "ax", %progbits .global rt_start .type rt_start, %function rt_start: bl rt_start_context cps MODE_SYS mpuconfigure mov sp, r0 #if RT_ARM_FP // Load the initial FPEXC and skip past non-volatile registers. ldr r1, [sp], 36 vmsr fpexc, r1 #else // !RT_ARM_FP // Skip past non-volatile registers. add sp, 32 #endif // RT_ARM_FP // Load the arguments to rt_task_entry and skip past {r2, r3, r12, lr}. ldrd r0, r1, [sp], 24 rfeia sp! .size rt_start, .-rt_start .section .text.rt_task_entry, "ax", %progbits .global rt_task_entry .type rt_task_entry, %function rt_task_entry: blx r1 /* The call to rt_task_exit is a tail call to a non-returning function, so * b or bl could be used, but bl can be fixed up by the linker to blx in * case a mode switch is needed between rt_task_entry and rt_task_exit. */ bl rt_task_exit .size rt_task_entry, .-rt_task_entry