Skip to content

Arch arm fix is in isr #19688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 24, 2019
3 changes: 3 additions & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ config ARCH_HAS_NOCACHE_MEMORY_SUPPORT
config ARCH_HAS_RAMFUNC_SUPPORT
bool

config ARCH_HAS_NESTED_EXCEPTION_DETECTION
bool

#
# Other architecture related options
#
Expand Down
1 change: 1 addition & 0 deletions arch/arm/core/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ config CPU_CORTEX_M
select ARCH_HAS_USERSPACE if ARM_MPU
select ARCH_HAS_NOCACHE_MEMORY_SUPPORT if ARM_MPU && CPU_HAS_ARM_MPU && CPU_CORTEX_M7
select ARCH_HAS_RAMFUNC_SUPPORT
select ARCH_HAS_NESTED_EXCEPTION_DETECTION
select SWAP_NONATOMIC
help
This option signifies the use of a CPU of the Cortex-M family.
Expand Down
202 changes: 140 additions & 62 deletions arch/arm/core/cortex_m/fault.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ LOG_MODULE_DECLARE(os);
#define EACD(edr) (((edr) & SYSMPU_EDR_EACD_MASK) >> SYSMPU_EDR_EACD_SHIFT)
#endif

#if defined(CONFIG_ARM_SECURE_FIRMWARE) || \
defined(CONFIG_ARM_NONSECURE_FIRMWARE)

/* Exception Return (EXC_RETURN) is provided in LR upon exception entry.
* It is used to perform an exception return and to detect possible state
* transition upon exception.
Expand Down Expand Up @@ -102,7 +99,6 @@ LOG_MODULE_DECLARE(os);
* to the Secure stack during a Non-Secure exception entry.
*/
#define ADDITIONAL_STATE_CONTEXT_WORDS 10
#endif /* CONFIG_ARM_SECURE_FIRMWARE || CONFIG_ARM_NONSECURE_FIRMWARE */

/**
*
Expand Down Expand Up @@ -777,94 +773,78 @@ static void secure_stack_dump(const z_arch_esf_t *secure_esf)
#endif /* CONFIG_FAULT_DUMP== 2 */
#endif /* CONFIG_ARM_SECURE_FIRMWARE */

/**
*
* @brief ARM Fault handler
*
* This routine is called when fatal error conditions are detected by hardware
* and is responsible for:
* - resetting the processor fault status registers (for the case when the
* error handling policy allows the system to recover from the error),
* - reporting the error information,
* - determining the error reason to be provided as input to the user-
* provided routine, k_sys_fatal_error_handler().
* The k_sys_fatal_error_handler() is invoked once the above operations are
* completed, and is responsible for implementing the error handling policy.
*
* The provided ESF pointer points to the exception stack frame of the current
* security state. Note that the current security state might not be the actual
* state in which the processor was executing, when the exception occurred.
* The actual state may need to be determined by inspecting the EXC_RETURN
* value, which is provided as argument to the Fault handler.
/*
* This internal function does the following:
*
* @param esf Pointer to the exception stack frame of the current security
* state. The stack frame may be either on the Main stack (MSP) or Process
* stack (PSP) depending at what execution state the exception was taken.
* - Retrieves the exception stack frame
* - Evaluates whether to report being in a nested exception
*
* @param exc_return EXC_RETURN value present in LR after exception entry.
* If the ESF is not successfully retrieved, the function signals
* an error by returning NULL.
*
* Note: exc_return argument shall only be used by the Fault handler if we are
* running a Secure Firmware.
* @return ESF pointer on success, otherwise return NULL
*/
void z_arm_fault(z_arch_esf_t *esf, u32_t exc_return)
static inline z_arch_esf_t *get_esf(u32_t msp, u32_t psp, u32_t exc_return,
bool *nested_exc)
{
u32_t reason = K_ERR_CPU_EXCEPTION;
int fault = SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk;
bool recoverable;
bool alternative_state_exc = false;
z_arch_esf_t *ptr_esf;

*nested_exc = false;

#if defined(CONFIG_ARM_SECURE_FIRMWARE)
if ((exc_return & EXC_RETURN_INDICATOR_PREFIX) !=
EXC_RETURN_INDICATOR_PREFIX) {
/* Invalid EXC_RETURN value */
goto _exit_fatal;
/* Invalid EXC_RETURN value. This is a fatal error. */
return NULL;
}

#if defined(CONFIG_ARM_SECURE_FIRMWARE)
if ((exc_return & EXC_RETURN_EXCEPTION_SECURE_Secure) == 0U) {
/* Secure Firmware shall only handle Secure Exceptions.
* This is a fatal error.
*/
goto _exit_fatal;
return NULL;
}

if (exc_return & EXC_RETURN_RETURN_STACK_Secure) {
/* Exception entry occurred in Secure stack. */
} else {
/* Exception entry occurred in Non-Secure stack. Therefore, 'esf'
* holds the Secure stack information, however, the actual
/* Exception entry occurred in Non-Secure stack. Therefore,
* msp/psp point to the Secure stack, however, the actual
* exception stack frame is located in the Non-Secure stack.
*/
alternative_state_exc = true;

/* Dump the Secure stack before handling the actual fault. */
SECURE_STACK_DUMP(esf);
z_arch_esf_t *secure_esf;

if (exc_return & EXC_RETURN_SPSEL_PROCESS) {
/* Secure stack pointed by PSP */
secure_esf = (z_arch_esf_t *)psp;
} else {
/* Secure stack pointed by MSP */
secure_esf = (z_arch_esf_t *)msp;
*nested_exc = true;
}

SECURE_STACK_DUMP(secure_esf);

/* Handle the actual fault.
* Extract the correct stack frame from the Non-Secure state
* and supply it to the fault handing function.
*/
if (exc_return & EXC_RETURN_MODE_THREAD) {
esf = (z_arch_esf_t *)__TZ_get_PSP_NS();
if ((SCB->ICSR & SCB_ICSR_RETTOBASE_Msk) == 0) {
PR_EXC("RETTOBASE does not match EXC_RETURN");
goto _exit_fatal;
}
ptr_esf = (z_arch_esf_t *)__TZ_get_PSP_NS();
} else {
esf = (z_arch_esf_t *)__TZ_get_MSP_NS();
if ((SCB->ICSR & SCB_ICSR_RETTOBASE_Msk) != 0) {
PR_EXC("RETTOBASE does not match EXC_RETURN");
goto _exit_fatal;
}
ptr_esf = (z_arch_esf_t *)__TZ_get_MSP_NS();
}
}
#elif defined(CONFIG_ARM_NONSECURE_FIRMWARE)
if ((exc_return & EXC_RETURN_INDICATOR_PREFIX) !=
EXC_RETURN_INDICATOR_PREFIX) {
/* Invalid EXC_RETURN value */
goto _exit_fatal;
}
if (exc_return & EXC_RETURN_EXCEPTION_SECURE_Secure) {
/* Non-Secure Firmware shall only handle Non-Secure Exceptions.
* This is a fatal error.
*/
goto _exit_fatal;
return NULL;
}

if (exc_return & EXC_RETURN_RETURN_STACK_Secure) {
Expand All @@ -875,22 +855,120 @@ void z_arm_fault(z_arch_esf_t *esf, u32_t exc_return)
* inspection will indicate the Non-Secure instruction
* that performed the branch to the Secure domain.
*/
alternative_state_exc = true;

PR_FAULT_INFO("Exception occurred in Secure State");

if (exc_return & EXC_RETURN_SPSEL_PROCESS) {
/* Non-Secure stack frame on PSP */
ptr_esf = (z_arch_esf_t *)psp;
} else {
/* Non-Secure stack frame on MSP */
ptr_esf = (z_arch_esf_t *)msp;
}
} else {
/* Exception entry occurred in Non-Secure stack. */
}
#else
(void) exc_return;
/* The processor has a single execution state.
* We verify that the Thread mode is using PSP.
*/
if ((exc_return & EXC_RETURN_MODE_THREAD) &&
(!(exc_return & EXC_RETURN_SPSEL_PROCESS))) {
PR_EXC("SPSEL in thread mode does not indicate PSP");
return NULL;
}
#endif /* CONFIG_ARM_SECURE_FIRMWARE */

if (!alternative_state_exc) {
if (exc_return & EXC_RETURN_MODE_THREAD) {
/* Returning to thread mode */
ptr_esf = (z_arch_esf_t *)psp;

} else {
/* Returning to handler mode */
ptr_esf = (z_arch_esf_t *)msp;
*nested_exc = true;
}
}

return ptr_esf;
}

/**
*
* @brief ARM Fault handler
*
* This routine is called when fatal error conditions are detected by hardware
* and is responsible for:
* - resetting the processor fault status registers (for the case when the
* error handling policy allows the system to recover from the error),
* - reporting the error information,
* - determining the error reason to be provided as input to the user-
* provided routine, k_sys_fatal_error_handler().
* The k_sys_fatal_error_handler() is invoked once the above operations are
* completed, and is responsible for implementing the error handling policy.
*
* The function needs, first, to determine the exception stack frame.
* Note that the current security state might not be the actual
* state in which the processor was executing, when the exception occurred.
* The actual state may need to be determined by inspecting the EXC_RETURN
* value, which is provided as argument to the Fault handler.
*
* If the exception occurred in the same security state, the stack frame
* will be pointed to by either MSP or PSP depending on the processor
* execution state when the exception occurred. MSP and PSP values are
* provided as arguments to the Fault handler.
*
* @param msp MSP value immediately after the exception occurred
* @param psp PSP value immediately after the exception occurred
* @param exc_return EXC_RETURN value present in LR after exception entry.
*
*/
void z_arm_fault(u32_t msp, u32_t psp, u32_t exc_return)
{
u32_t reason = K_ERR_CPU_EXCEPTION;
int fault = SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk;
bool recoverable, nested_exc;
z_arch_esf_t *esf;

/* Create a stack-ed copy of the ESF to be used during
* the fault handling process.
*/
z_arch_esf_t esf_copy;

/* Force unlock interrupts */
z_arch_irq_unlock(0);

/* Retrieve the Exception Stack Frame (ESF) to be supplied
* as argument to the remainder of the fault handling process.
*/
esf = get_esf(msp, psp, exc_return, &nested_exc);
__ASSERT(esf != NULL,
"ESF could not be retrieved successfully. Shall never occur.");

reason = fault_handle(esf, fault, &recoverable);
if (recoverable) {
return;
}

#if defined(CONFIG_ARM_SECURE_FIRMWARE) || \
defined(CONFIG_ARM_NONSECURE_FIRMWARE)
_exit_fatal:
#endif
z_arm_fatal_error(reason, esf);
/* Copy ESF */
memcpy(&esf_copy, esf, sizeof(z_arch_esf_t));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to make a copy of the ESF?
AFAIK every other platform just passes the original stack frame along.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason is that kernel/fatal.c needs to query, whether this a nested exception. And it needs to do it by inspecting the ESF. So the ESF must be holding correct information. The problem with the ARM ESF is that it may be, originally, corrupted due to e.g. stack overflow.

The only GENERIC way for ARM Software to detect if it is in a nested exception, is to inspect the EXC_RETURN value that is placed in LR upon exception entry. But EXC_RETURN value is neither part of ESF, nor stored in fault.c (as a global state variable), nor is it passed to kernel/fatal.c as argument.

So, unless we want to change the internal API of z_fatal_error(), for instance, by adding a "flags" parameter, we need the sole parameter, *esf, to hold all the required info. That's why I do the copy here.

if we define z_fatal_error(*esf, some_generic_flags) { }, we should be able to pass the EXC_RETURN value or the nested_exception boolean value, but all these require cross-arch changes.


/* Overwrite stacked IPSR to mark a nested exception,
* or a return to Thread mode. Note that this may be
* required, if the retrieved ESF contents are invalid
* due to, for instance, a stacking error.
*/
if (nested_exc) {
if ((esf_copy.basic.xpsr & IPSR_ISR_Msk) == 0) {
esf_copy.basic.xpsr |= IPSR_ISR_Msk;
}
} else {
esf_copy.basic.xpsr &= ~(IPSR_ISR_Msk);
}

z_arm_fatal_error(reason, &esf_copy);
}

/**
Expand Down
15 changes: 15 additions & 0 deletions arch/arm/core/fatal.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ void z_arm_fatal_error(unsigned int reason, const z_arch_esf_t *esf)
z_fatal_error(reason, esf);
}

/**
* @brief Handle a software-generated fatal exception
* (e.g. kernel oops, panic, etc.).
*
* Notes:
* - the function is invoked in SVC Handler
* - if triggered from nPRIV mode, only oops and stack fail error reasons
* may be propagated to the fault handling process.
* - We expect the supplied exception stack frame to always be a valid
* frame. That is because, if the ESF cannot be stacked during an SVC,
* a processor fault (e.g. stacking error) will be generated, and the
* fault handler will executed insted of the SVC.
*
* @param esf exception frame
*/
void z_do_kernel_oops(const z_arch_esf_t *esf)
{
/* Stacked R0 holds the exception reason. */
Expand Down
Loading