STM32 TrustZone development and debugging skills | HardFault debugging and processing
Introduction
In the first two articles on STM32 TrustZone development and debugging skills , we introduced the kernel's SAU/IDAU, address security attribute configuration, resource security attribute configuration, kernel security rules for accessing resources, and common peripherals used in the TrustZone environment. questions, etc. Another common problem you may encounter when developing a TrustZone environment is software-triggered failure errors. The exception model and Fault processing in the TrustZone environment of the ARM CM33 kernel have many changes from those without security extensions. Once a HardFault occurs, inexperienced developers may not have a clue and don't know where to start to find the problem. Therefore, the focus of this article will be around the exception model in the CM33 TrustZone environment and the debugging and processing of HardFault for developers' reference.
1. Anomaly model under CM33 TrustZone architecture
HardFault is the default Fault exception and is always enabled. The reason for triggering may be that the exception handling itself triggers an error, or an exception cannot be handled by other mechanisms and rises to HardFault. It has a higher priority than all other exceptions with configurable priority.
It should be noted that even if AIRCR.BFHFNMINS=1, exceptions originally targeted to the S side and raised to HardFault will still trigger HardFault on the S side. They are not affected by the AIRCR.BFHFNMINS bit, such as when security code violates MPU protection. According to the rules, when a MemManage error occurs, even if AIRCR.BFHFNMINS=1, the fault will still enter the Secure HardFault Handler. The HardFault on the NS side may be triggered only when AIRCR.BFHFNMINS=1.
void SECURE_SetNMIHFBFTarget(int NS)
{
uint32_t reg_value;
uint32_t target = (NS==1)?1:0;
/* read old register configuration */
reg_value = SCB->AIRCR;
/* clear bits to change */
reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_BFHFNMINS_Msk));
/* insert write key and target bit */
reg_value = (reg_value |
((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(target << SCB_AIRCR_BFHFNMINS_Pos) );
SCB->AIRCR = reg_value;
}
Note : Sometimes, software may need to set the AIRCR.PRIS bit to reduce the overall priority of NS interrupts (for example, this mechanism is used in the implementation of TF-M). At this time, if AIRCR.PRIS=1 and AIRCR.BFHFNMINS=1 are set at the same time, the behavior of the kernel will be unpredictable. Therefore, if you need to set AIRCR.PRIS=1, it is recommended to keep AIRCR.BFHFNMINS=0.
1.1.2. Bus Fault
Bus Fault usually occurs during instruction or data access, and may be caused . Bus Fault is not enabled by default, which means that a bus fault will trigger the HardFault Handler by default. If you need to enable Bus Fault separately, you can set the SHCSR.BUSFAULTENA bit of SCB to 1.
In the TrustZone environment, the Bus Fault does not belong to the Bank. Whether the BusFault Handler on the S or NS side is triggered is related to AIRCR.BFHFNMINS of SCB. If AIRCR.BFHFNMINS=0, BusFault always targets the S safe state; otherwise, if AIRCR.BFHFNMINS=1, the target is NS non-safe state.
Normally, bus fault information is marked in the CFSR/BFSR and BFAR registers of the SCB. In the TrustZone environment, some registers of the SCB and some bits of the register belong to the Bank. The respective SCB registers can be seen from both the secure side and the non-secure side, but the BFSR field of the CFSR register and the BFAR register do not belong to the Bank. The Bus fault may target the S safe side or the NS non-safe side. When a bus error occurs, if the Bus Fault information is read from the relevant registers of SCB_S and SCB_NS respectively, different results can be seen.
If AIRCR.BFHFNMINS=0, only the secure side can see the real data of BFSR and BFAR. If the non-secure side reads BFSR, BFAR, or reads BFSR_NS from the secure side, BFAR_NS can only read the value of all 0s.
If AIRCR.BFHFNMINS=1, the values of BFAR_NS and BFAR_S will generally read the same value. Usually, when the code needs to handle BusFault, if the default configuration is used, that is, keeping the BusFault target to the S side and AIRCR.BFHFNMINS=0, the Fault Handler can obtain the bus fault information from the CFSR.BFSR and BFAR registers of SCB_S; and if AIRCR is set .BFHFNMINS=1, then when a Bus error occurs, the Fault Handler on the non-safety side can directly obtain fault information from the CFSR.BFSR and BFAR registers of SCB_NS.
void EnableBusFault(int enable)
{
if( enable == 1)
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk;
else
SCB->SHCSR &= (~SCB_SHCSR_BUSFAULTENA_Msk);
}
This code is the same for both the security and non-security sides, but it should be noted that since BusFault does not belong to the Bank, when AIRCR.BFHFNMINS=0, this code can only be used on the security side, that is, the S security side is modified. BusFault of SCB SHCSR, writing the SHCSR.BUSFAULTENA bit of SCB_NS is invalid at this time.
If the non-secure side application uses this code to enable BusFault, the premise is that the secure side has set AIRCR.BFHFNMINS=1.
UsageFault is related to errors during instruction execution, including undefined instructions, non-aligned access, invalid status when executing instructions, errors during interrupt return, division by 0, etc.
In the TrustZone environment, UsageFault belongs to Bank, so respective UsageFault may occur in S and NS states, and respective S UsageFault Handler and NS UsageFault Handler may be triggered. UsageFault is not enabled by default, so it will rise to HardFault by default. Whether to trigger the S or NS HardFault Handler depends on whether the value of AIRCR.BFHFNMINS is 0 or 1.
Enabling UsageFault requires setting SHCSR.USGFAULTENA of SCB_S and SCB_NS respectively. SHCSR.USGFAULTENA=1 of SCB_S is used to enable Usage Fault on the S safe side; SHCSR.USGFAULTENA=1 of SCB_NS is used to enable Usage Fault on the NS non-safe side.
In addition, usually the division by 0 operation will not trigger UsageFault. If you want the division by 0 operation to trigger UsageFault, you need to set the CCR.DIV_0_TRP bit corresponding to SCB_S/NS to 1.
As long as SHCSR.USGFAULTENA=1, UsageFault will always trigger the UsageFault Handler corresponding to the security state of the software. Otherwise, it will rise to HardFault. UsageFault on the security side will always rise to Secure HardFault. For UsageFault on the non-security side, if AIRCR.BFHFNMINS=0, it rises to Secure HardFault, otherwise it rises to Non-Secure HardFault.
void EnableUsageFault(int enable)
{
if( enable == 1)
{
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
/* Enable divide by 0 trap to trigger usage fault (if necessary) */
SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
}
else
SCB->SHCSR &= (~SCB_SHCSR_USGFAULTENA_Msk);
}
void EnableNSUsageFault(int enable)
{
if( enable == 1)
SCB_NS->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
else
SCB_NS->SHCSR &= (~SCB_SHCSR_USGFAULTENA_Msk);
}
MemManage Fault is a fault exception caused by Memory protection, such as violating the access rules defined by the MPU region when fetching instructions or performing data access, or violating the default address protection rules.
MemManageFault is similar to UsageFault and is not enabled by default. If you want to enable MemManageFault on the S or NS side, you need to set the SHCSR.MEMFAULTENA bit of SCB_S or SCB_NS accordingly.
In addition, similar to UsageFault, MemManageFault is also Bank on the S and NS sides, that is, S and NS have their own MemManageFault. Since the MPU unit itself belongs to the Bank, there are two sets of MPU registers MPU_S and MPU_NS in the system. Therefore, the code on the S and NS sides can each define its own MPU region and use different configurations. That is to say, even for the same address, S/NS Both sides can also define different access rules through their respective MPU units. The protection rules configured in MPU_S only apply to the S security side code, that is, to control access when the CPU is in a safe state. This has nothing to do with the security attributes defined in the SAU for the addresses accessed by the CPU. The protection rules configured in MPU_NS only apply to NS non-safe side code, that is, access when the CPU is in a non-safe state, and the two do not affect each other.
void EnableMemoryFault(int enable)
{
if( enable == 1)
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
else
SCB->SHCSR &= (~SCB_SHCSR_MEMFAULTENA_Msk);
}
void EnableNSMemoryFault (int enable)
{
if( enable == 1)
SCB_NS->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
else
SCB_NS->SHCSR &= (~SCB_SHCSR_MEMFAULTENA_Msk);
}
In addition, if the code uses the HAL API to enable the MPU, that is, calls HAL_MPU_Enable(), then MemManage Fault will be automatically enabled in the MPU enabling function. At this time, there is no need to call the previously mentioned code to separately enable MemManage Fault.
1.1.5. Secure Fault
Secure Fault only exists in an environment where TrustZone is enabled. SecureFault may be triggered due to various security checks in the kernel, such as jumping from NS to S code without entering from the SG entry instruction, or non-security code trying to access the safe address range specified by SAU/IDAU. Usually when a SecureFault occurs, the software can directly stop or reset the system. This can avoid introducing security vulnerabilities as much as possible.
void EnableSecureFault(int enable)
{
if( enable == 1)
SCB->SHCSR |= SCB_SHCSR_SECUREFAULTENA_Msk;
else
SCB->SHCSR &= (~SCB_SHCSR_SECUREFAULTENA_Msk);
}
Except for HardFault, other fault types have configurable priorities. Software can disable a certain priority-configurable fault exception, but cannot disable HardFault. The priority of the fault exception and the corresponding mask bit determine whether the kernel will enter the handler of a certain fault, and whether a certain fault can preempt another fault.
-
This fault Fault is not enabled;
-
The fault's FaultHandler priority is not high enough to run;
-
The same fault occurred in the faulty FaultHandler;
Bus errors that occur when obtaining the exception vector are always upgraded to HardFault and are handled by HardFault instead of BusFault.
▼▼▼
© THE END
Follow STM32