From 4760aad353a88c4778ad6bd15720bc8a9a2b4162 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Wed, 24 Jan 2024 12:07:20 +0000 Subject: [PATCH] arch: arm: cortex_m: Convert cpu_idle from ASM to C Asm is notoriously harder to maintain than C and requires core specific adaptation which impairs even more the readability of the code. This change reduces the need for core specific conditional compilation and unifies irq locking code. Signed-off-by: Wilfried Chauveau # Conflicts: # soc/arm/nordic_nrf/nrf53/soc_cpu_idle.h --- arch/arm/core/cortex_m/CMakeLists.txt | 2 +- arch/arm/core/cortex_m/cpu_idle.S | 201 -------------------------- arch/arm/core/cortex_m/cpu_idle.c | 128 ++++++++++++++++ soc/nordic/nrf53/soc_cpu_idle.h | 25 ++-- 4 files changed, 143 insertions(+), 213 deletions(-) delete mode 100644 arch/arm/core/cortex_m/cpu_idle.S create mode 100644 arch/arm/core/cortex_m/cpu_idle.c diff --git a/arch/arm/core/cortex_m/CMakeLists.txt b/arch/arm/core/cortex_m/CMakeLists.txt index 9a24e7f60b..b220e6c81e 100644 --- a/arch/arm/core/cortex_m/CMakeLists.txt +++ b/arch/arm/core/cortex_m/CMakeLists.txt @@ -16,7 +16,7 @@ zephyr_library_sources( irq_manage.c prep_c.c thread.c - cpu_idle.S + cpu_idle.c ) zephyr_library_sources_ifndef(CONFIG_ARM_CUSTOM_INTERRUPT_CONTROLLER irq_init.c) diff --git a/arch/arm/core/cortex_m/cpu_idle.S b/arch/arm/core/cortex_m/cpu_idle.S deleted file mode 100644 index 8acae5ddb1..0000000000 --- a/arch/arm/core/cortex_m/cpu_idle.S +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2013-2014 Wind River Systems, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file - * @brief ARM Cortex-M power management - * - */ - -#include -#include - -#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) -#include -#endif - -_ASM_FILE_PROLOGUE - -GTEXT(z_arm_cpu_idle_init) -GTEXT(arch_cpu_idle) -GTEXT(arch_cpu_atomic_idle) - -#define _SCB_SCR 0xE000ED10 - -#define _SCB_SCR_SEVONPEND (1 << 4) -#define _SCB_SCR_SLEEPDEEP (1 << 2) -#define _SCB_SCR_SLEEPONEXIT (1 << 1) -#define _SCR_INIT_BITS _SCB_SCR_SEVONPEND - -.macro _sleep_if_allowed wait_instruction -#if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK) - push {r0, lr} - bl z_arm_on_enter_cpu_idle - /* Skip the wait instruction if on_enter_cpu_idle() returns false. */ - cmp r0, #0 - beq _skip_\@ -#endif /* CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK */ - - /* - * Wait for all memory transactions to complete before entering low - * power state. - */ - dsb - \wait_instruction - -#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) - /* Inline the macro provided by SoC-specific code */ - SOC_ON_EXIT_CPU_IDLE -#endif /* CONFIG_ARM_ON_EXIT_CPU_IDLE */ - -#if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK) -_skip_\@: -#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) - pop {r0, r1} - mov lr, r1 -#else - pop {r0, lr} -#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ -#endif /* CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK */ -.endm - -/** - * - * @brief Initialization of CPU idle - * - * Only called by arch_kernel_init(). Sets SEVONPEND bit once for the system's - * duration. - * - * C function prototype: - * - * void z_arm_cpu_idle_init(void); - */ - -SECTION_FUNC(TEXT, z_arm_cpu_idle_init) - ldr r1, =_SCB_SCR - movs.n r2, #_SCR_INIT_BITS - str r2, [r1] - bx lr - -SECTION_FUNC(TEXT, arch_cpu_idle) -#if defined(CONFIG_TRACING) || \ - defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK) - push {r0, lr} - -#ifdef CONFIG_TRACING - bl sys_trace_idle -#endif -#ifdef CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK - bl z_arm_on_enter_cpu_idle_prepare -#endif - -#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) - pop {r0, r1} - mov lr, r1 -#else - pop {r0, lr} -#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ -#endif - -#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) - /* - * PRIMASK is always cleared on ARMv7-M and ARMv8-M Mainline (not used - * for interrupt locking), and configuring BASEPRI to the lowest - * priority to ensure wake-up will cause interrupts to be serviced - * before entering low power state. - * - * Set PRIMASK before configuring BASEPRI to prevent interruption - * before wake-up. - */ - cpsid i - - /* - * Set wake-up interrupt priority to the lowest and synchronise to - * ensure that this is visible to the WFI instruction. - */ - eors.n r0, r0 - msr BASEPRI, r0 - isb -#else - /* - * For all the other ARM architectures that do not implement BASEPRI, - * PRIMASK is used as the interrupt locking mechanism, and it is not - * necessary to set PRIMASK here, as PRIMASK would have already been - * set by the caller as part of interrupt locking if necessary - * (i.e. if the caller sets _kernel.idle). - */ -#endif /* CONFIG_ARMV7_M_ARMV8_M_MAINLINE */ - - /* Enter low power state */ - _sleep_if_allowed wfi - - /* - * Clear PRIMASK and flush instruction buffer to immediately service - * the wake-up interrupt. - */ - cpsie i - isb - - bx lr - -SECTION_FUNC(TEXT, arch_cpu_atomic_idle) -#if defined(CONFIG_TRACING) || \ - defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK) - push {r0, lr} - -#ifdef CONFIG_TRACING - bl sys_trace_idle -#endif -#ifdef CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK - bl z_arm_on_enter_cpu_idle_prepare -#endif - -#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) - pop {r0, r1} - mov lr, r1 -#else - pop {r0, lr} -#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ -#endif - /* - * Lock PRIMASK while sleeping: wfe will still get interrupted by - * incoming interrupts but the CPU will not service them right away. - */ - cpsid i - - /* - * No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init() - * and never touched again. - */ - - /* r0: interrupt mask from caller */ - -#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) - /* No BASEPRI, call wfe directly - * (SEVONPEND is set in z_arm_cpu_idle_init()) - */ - _sleep_if_allowed wfe - - cmp r0, #0 - bne _irq_disabled - cpsie i -_irq_disabled: - -#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) - /* r1: zero, for setting BASEPRI (needs a register) */ - eors.n r1, r1 - - /* unlock BASEPRI so wfe gets interrupted by incoming interrupts */ - msr BASEPRI, r1 - - _sleep_if_allowed wfe - - msr BASEPRI, r0 - cpsie i -#else -#error Unknown ARM architecture -#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ - bx lr diff --git a/arch/arm/core/cortex_m/cpu_idle.c b/arch/arm/core/cortex_m/cpu_idle.c new file mode 100644 index 0000000000..39f3c7de77 --- /dev/null +++ b/arch/arm/core/cortex_m/cpu_idle.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013-2014 Wind River Systems, Inc. + * Copyright (c) 2023 Arm Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ARM Cortex-M power management + */ +#include +#include + +#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) +#include +#endif + +/** + * @brief Initialization of CPU idle + * + * Only called by arch_kernel_init(). Sets SEVONPEND bit once for the system's + * duration. + */ +void z_arm_cpu_idle_init(void) +{ + SCB->SCR = SCB_SCR_SEVONPEND_Msk; +} + +#if defined(CONFIG_ARM_ON_EXIT_CPU_IDLE) +#define ON_EXIT_IDLE_HOOK SOC_ON_EXIT_CPU_IDLE +#else +#define ON_EXIT_IDLE_HOOK do {} while (false) +#endif + +#if defined(CONFIG_ARM_ON_ENTER_CPU_IDLE_HOOK) +#define SLEEP_IF_ALLOWED(wait_instr) do { \ + if (!z_arm_on_enter_cpu_idle()) { \ + __DSB(); \ + wait_instr(); \ + ON_EXIT_IDLE_HOOK; \ + } \ +} while (false) +#else +#define SLEEP_IF_ALLOWED(wait_instr) do { \ + __DSB(); \ + wait_instr(); \ + ON_EXIT_IDLE_HOOK; \ +} while (false) +#endif + +void arch_cpu_idle(void) +{ +#if defined(CONFIG_TRACING) + sys_trace_idle(); +#endif + +#if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK + z_arm_on_enter_cpu_idle_prepare(); +#endif + +#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) + /* + * PRIMASK is always cleared on ARMv7-M and ARMv8-M (not used + * for interrupt locking), and configuring BASEPRI to the lowest + * priority to ensure wake-up will cause interrupts to be serviced + * before entering low power state. + * + * Set PRIMASK before configuring BASEPRI to prevent interruption + * before wake-up. + */ + __disable_irq(); + + /* + * Set wake-up interrupt priority to the lowest and synchronise to + * ensure that this is visible to the WFI instruction. + */ + __set_BASEPRI(0); + __ISB(); +#else + /* + * For all the other ARM architectures that do not implement BASEPRI, + * PRIMASK is used as the interrupt locking mechanism, and it is not + * necessary to set PRIMASK here, as PRIMASK would have already been + * set by the caller as part of interrupt locking if necessary + * (i.e. if the caller sets _kernel.idle). + */ +#endif + + /* + * Wait for all memory transactions to complete before entering low + * power state. + */ + SLEEP_IF_ALLOWED(__WFI); + + __enable_irq(); + __ISB(); +} + +void arch_cpu_atomic_idle(unsigned int key) +{ +#if defined(CONFIG_TRACING) + sys_trace_idle(); +#endif + +#if CONFIG_ARM_ON_ENTER_CPU_IDLE_PREPARE_HOOK + z_arm_on_enter_cpu_idle_prepare(); +#endif + + /* + * Lock PRIMASK while sleeping: wfe will still get interrupted by + * incoming interrupts but the CPU will not service them right away. + */ + __disable_irq(); + + /* + * No need to set SEVONPEND, it's set once in z_arm_cpu_idle_init() + * and never touched again. + */ + + /* + * Wait for all memory transactions to complete before entering low + * power state. + */ + SLEEP_IF_ALLOWED(__WFE); + + arch_irq_unlock(key); +} diff --git a/soc/nordic/nrf53/soc_cpu_idle.h b/soc/nordic/nrf53/soc_cpu_idle.h index c02c945141..d17b0e1544 100644 --- a/soc/nordic/nrf53/soc_cpu_idle.h +++ b/soc/nordic/nrf53/soc_cpu_idle.h @@ -8,19 +8,22 @@ * @file SoC extensions of cpu_idle.S for the Nordic Semiconductor nRF53 processors family. */ - -#if defined(_ASMLANGUAGE) +#define SOC_ON_EXIT_CPU_IDLE_4 \ + __NOP(); \ + __NOP(); \ + __NOP(); \ + __NOP(); +#define SOC_ON_EXIT_CPU_IDLE_8 \ + SOC_ON_EXIT_CPU_IDLE_4 \ + SOC_ON_EXIT_CPU_IDLE_4 #if defined(CONFIG_SOC_NRF53_ANOMALY_168_WORKAROUND_FOR_EXECUTION_FROM_RAM) #define SOC_ON_EXIT_CPU_IDLE \ - .rept 26; \ - nop; \ - .endr + SOC_ON_EXIT_CPU_IDLE_8; \ + SOC_ON_EXIT_CPU_IDLE_8; \ + SOC_ON_EXIT_CPU_IDLE_8; \ + __NOP(); \ + __NOP(); #elif defined(CONFIG_SOC_NRF53_ANOMALY_168_WORKAROUND) -#define SOC_ON_EXIT_CPU_IDLE \ - .rept 8; \ - nop; \ - .endr +#define SOC_ON_EXIT_CPU_IDLE SOC_ON_EXIT_CPU_IDLE_8 #endif - -#endif /* _ASMLANGUAGE */