Context:
I'm testing the tfm_psa_template sample on the nRF9151DK and I got confused as to why I lose access to the debug port.
As required by the sample, I first flash the provisioning_image sample to write keys to OTP. After flashing this sample, I can still access the debug port without any issues.
However, once I flash the tfm_psa_template, I completely lose access to the debug port.
Interestingly, if I erase the flash and re-flash the tfm_psa_template, I regain access to the debug port.
This leads me to believe that something in the provisioning_image sample is causing the loss of debug port access.
From my understanding, the NRF_APPROTECT_HANDLING configuration controls access to the debug port for non-secure images, while NRF_SECURE_APPROTECT_HANDLING controls access for secure images.
By default, NRF_APPROTECT_HANDLING is set to NRF_APPROTECT_USE_UICR, assuming its dependencies have been met. Here's a reference from zephyr/soc/nordic/KConfig:
choice NRF_APPROTECT_HANDLING bool "APPROTECT handling" depends on SOC_SERIES_NRF52X || SOC_NRF5340_CPUNET || \ (SOC_NRF5340_CPUAPP && !TRUSTED_EXECUTION_NONSECURE) || \ SOC_SERIES_NRF91X default NRF_APPROTECT_USE_UICR help Specifies how the SystemInit() function should handle the APPROTECT mechanism.
Additional compilation definitions are created for NRF_APPROTECT_LOCK and NRF_APPROTECT_USER_HANDLING. Here's a reference from zephyr/modules/hal_nordic/nrfx/CMakeLists.txt:
zephyr_compile_definitions_ifdef(CONFIG_NRF_APPROTECT_LOCK ENABLE_APPROTECT) zephyr_compile_definitions_ifdef(CONFIG_NRF_APPROTECT_USER_HANDLING ENABLE_APPROTECT_USER_HANDLING) zephyr_compile_definitions_ifdef(CONFIG_NRF_SECURE_APPROTECT_LOCK ENABLE_SECURE_APPROTECT) zephyr_compile_definitions_ifdef(CONFIG_NRF_SECURE_APPROTECT_USER_HANDLING ENABLE_SECURE_APPROTECT_USER_HANDLING)
These compilation definitions control the behavior of nrf_handle_approtect(void). Here's a reference from modules/hal/nordic/mdk/system_nrf91_approtect.h:
static inline void nrf91_handle_approtect(void) { if (!nrf91_errata_36()) { /* Target device does not support firmware-driven approtect. */ return; } #if defined (NRF91_ERRATA_36_PRESENT) && NRF91_ERRATA_36_PRESENT #if defined (ENABLE_APPROTECT) /* Prevent processor from unlocking APPROTECT soft branch after this point. */ NRF_APPROTECT_S->APPROTECT.FORCEPROTECT = (APPROTECT_APPROTECT_FORCEPROTECT_FORCEPROTECT_Force << APPROTECT_APPROTECT_FORCEPROTECT_FORCEPROTECT_Pos); #elif defined (ENABLE_APPROTECT_USER_HANDLING) /* Do nothing, allow user code to handle APPROTECT. Use this if you want to enable authenticated debug. */ #else /* Load APPROTECT soft branch from UICR. If UICR->APPROTECT is disabled, APPROTECT->APPROTECT will be disabled. */ NRF_APPROTECT_S->APPROTECT.DISABLE = NRF_UICR_S->APPROTECT == UICR_APPROTECT_PALL_HwUnprotected ? APPROTECT_APPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul; #endif /* Secure APPROTECT is only available for Application core. */ #if defined (ENABLE_SECURE_APPROTECT) /* Prevent processor from unlocking SECURE APPROTECT soft branch after this point. */ NRF_APPROTECT_S->SECUREAPPROTECT.FORCEPROTECT = (APPROTECT_SECUREAPPROTECT_FORCEPROTECT_FORCEPROTECT_Force << APPROTECT_SECUREAPPROTECT_FORCEPROTECT_FORCEPROTECT_Pos); #elif defined (ENABLE_SECURE_APPROTECT_USER_HANDLING) /* Do nothing, allow user code to handle SECURE APPROTECT. Use this if you want to enable authenticated debug. */ #else /* Load SECURE APPROTECT soft branch from UICR. If UICR->SECUREAPPROTECT is disabled, APPROTECT->SECUREAPPROTECT will be disabled. */ NRF_APPROTECT_S->SECUREAPPROTECT.DISABLE = NRF_UICR_S->SECUREAPPROTECT == UICR_SECUREAPPROTECT_PALL_HwUnprotected ? APPROTECT_SECUREAPPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul; #endif #endif }
This function is called from SystemInit(void), but only for secure images. Here's a reference from modules/hal/nordic/nrfx/mdk/system_nrf91.c:
void SystemInit(void)
{
#if !defined(NRF_TRUSTZONE_NONSECURE)
/* [...] */
nrf91_handle_approtect();
#endif
/* [...] */
}
For the provisioning_image sample, the NRF_SECURE_APPROTECT_HANDLING is disabled because the SOC_NRF5340_CPUAPP dependency is not set.
Therefore, since the provisioning_image is a non-secure image, it will execute:
/* Load APPROTECT soft branch from UICR.
If UICR->APPROTECT is disabled, APPROTECT->APPROTECT will be disabled. */
NRF_APPROTECT_S->APPROTECT.DISABLE = NRF_UICR_S->APPROTECT == UICR_APPROTECT_PALL_HwUnprotected ? APPROTECT_APPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul;
... causing the debug port to become disabled!
As it is written to UICR it is persistent across image flashes, unless it is flashed with the recover option.
Questions:
- Is this behavior intentional? I wasn't able to find any reference in the documentation indicating that the debug probe would be disabled after flashing the provisioning_image. While there is a brief mention of disabling debugging access to the nRF5340 network core, there’s nothing about restricting debug access for the non-secure image. It seems like some default configuration may have been changed without proper documentation in the README.
- Does the tfm_psa_template also build a secure image? Is there any sample available that demonstrates the use-case of both secure and non-secure images? Is this the intended usage pattern for real-world deployment? If it's not, why is nrf_handle_approtect(void) only called from secure images? I'd assume that disabling debug port access is a fairly standard "ask" for real-world deployments. Or is it only intended that this access be disabled during provisioning? If so, could this be added to the documentation?