Debug Port Disabled After Flashing TF-M Provisioning Image on nRF9151DK: Is This Intentional?

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:

  1. 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.
  2. 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?
Related