Working with the nRF52 Series' improved APPROTECT

Working with the nRF52 Series' improved APPROTECT

New revisions of nRF52805 (revision 2, build codes Bx0), nRF52810 (revision 3, build codes Ex0), nrf52811 (revision 2, build codes Bx0), nRF52820 (revision 3, build codes Dx0), nRF52833 (revision 2, build codes Bx0), nRF52832 (revision 3, build codes Gx0), and nRF52840 (revision 3, build codes Fx0) include an improved implementation of the the access port protection mechanism.

The general advice for working with the new revisions of these devices is as follows:

In factory state, the [new version of this device] comes with the access port protection enabled. An ERASEALL command via the control access port (CTRL-AP) is required to enable access.

In order to lock the device debug port, execute the following steps to enable access port protection:

1. Start with a CTRL-AP ERASEALL operation.
2. Program code compiled with MDK 8.45.0 or later, with ENABLE_APPROTECT defined.
3. Write Enabled (0x00) to UICR.APPROTECT
4. Perform a hard reset to protect the device. The programmed code from step 2 will write APPROTECT.FORCEPROTECT to Force (0x00).

To unlock the device debug port (for debugging etc.), execute the following steps to disable access port protection:

1. Start with a CTRL-AP ERASEALL operation.
2. Program code compiled with an MDK 8.45.0 or later, without ENABLE_APPROTECT defined.
3. Write HwDisabled (0x5A) to UICR.APPROTECT
4. Perform any reset to run the code. The programmed code from step 2 will open access port by writing to APPROTECT.DISABLE during start-up.

The new access port protection mechanism is configured in two steps: in the hardware and in the firmware. At reset, the UICR.APPROTECT value is fetched in hardware. Then a function in MDK version 8.45.0 (or newer) is executed during startup to complete the firmware step. If the firmware step is skipped then the device may appear locked but it will not be completely protected.

Using the latest version of the MDK is recommended because the MDK also implements workarounds for errata.

Step 1: Including the MDK functionality

In the nRF5 SDK the ENABLE_APPROTECT symbol can be added to a project's preprocessor defines to ensure that the firmware side of the access port protection mechanism stays locked. In the nRF Connect SDK this is accomplished by adding CONFIG_NRF_APPROTECT_LOCK=y to a project's configuration. If the project does not explicitly lock itself then the code in the MDK will read the device's UICR.APPROTECT value and use this to complete step two of the access port protection's configuration. However, the code in the MDK does not set the UICR.APPROTECT value.

NOTE: If using the nRF5 SDK then ensure that it has been updated to MDK version 8.45.0 or newer.
NOTE: nRF Connect SDK version 1.9.0 or newer uses a valid MDK version.

Step 2: Setting UICR.APPROTECT

The most effective way to ensure that UICR.APPROTECT is set correctly is probably to verify it in the application. For example, the following code snippet could be added to the top of main:

#ifdef ENABLE_APPROTECT
	if ((NRF_UICR->APPROTECT & UICR_APPROTECT_PALL_Msk) !=
		(UICR_APPROTECT_PALL_Enabled << UICR_APPROTECT_PALL_Pos)) {

        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}

        NRF_UICR->APPROTECT = ((NRF_UICR->APPROTECT & ~((uint32_t)UICR_APPROTECT_PALL_Msk)) |
		    (UICR_APPROTECT_PALL_Enabled << UICR_APPROTECT_PALL_Pos));

        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
   	}
#else
	if ((NRF_UICR->APPROTECT & UICR_APPROTECT_PALL_Msk) !=
		(UICR_APPROTECT_PALL_HwDisabled << UICR_APPROTECT_PALL_Pos)) {

        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}

        NRF_UICR->APPROTECT = ((NRF_UICR->APPROTECT & ~((uint32_t)UICR_APPROTECT_PALL_Msk)) |
		    (UICR_APPROTECT_PALL_HwDisabled << UICR_APPROTECT_PALL_Pos));

        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy){}
	}
#endif

Simply ensure that this function is allowed to run -- followed by a pin, power, or brownout reset so it takes effect -- before the device leaves the factory.

Alternatively, a UICR.APPROTECT value can be added to the application's hex file. Although there are different ways to accomplish this, a simple python script was created as an example for this post.

Finally, in some situations it might be reasonable to set UICR.APPROTECT over SWD or from a host tool like nrfjprog. nrfjprog's recover operation writes UICR.APPROTECT to HwDisabled and programs a small piece of firmware to the flash to ensure that the device continues to unlock itself at reset.

$ nrfjprog --recover
    Recovering device. This operation might take 30s.
    Writing image to disable ap protect.
    Erasing user code and UICR flash areas.
$ nrfjprog --memrd 0x10001208
    0x10001208: 0000005A                              |Z...|
$ nrfjprog --program <path to hex> --sectorerase --verify
    Parsing image file.
    Verifying programming.
    Verified OK.
$ nrfjprog --memwr 0x10001208 --val 0x00
    Parsing parameters.
    WARNING: Writing to a non-empty address may result in unexpected behavior.
    Would you like to continue with the operation? [Y]/N y
    Writing.
$ nrfjprog --memrd 0x10001208
    0x10001208: 00000000

Unlocking: nrfjprog or CTRL-AP ERASEALL

After an ERASEALL operation is executed (by nrfjprog or manually via the CTRL-AP) the device should be accessible to a debugger until it executes a pin, power, or brownout reset.

The nrfjprog command line tool can be used to erase the device and write UICR.APPROTECT to HwDisabled (0x5A). Note that nrfjprog also writes a small application to the processor to keep it from locking itself after a reset. This does not prevent UICR.APPROTECT from being set to Enabled (0x00) later, e.g. when a real application is programmed, because flash bits can be written from 1 to 0.

$ nrfjprog --recover
	Recovering device. This operation might take 30s.
	Writing image to disable ap protect.
	Erasing user code and UICR flash areas.
$ nrfjprog --memrd 0x10001208
	0x10001208: 0000005A

If nrfjprog is not available then the CTRL-AP can also be accessed directly. Most Nordic Semiconductor DKs have an onboard J-Link so Segger's JLink utility is a convenient way to demonstrate how to interact with the CTRL-AP over SWD. Two Debug Port (DP) registers need to be introduced first:

CTRL/STAT register

Setting the CSYSPWRUPREQ and CDBGPWRUPREQ bits in the CTRL/STAT DP powers up the device's debugger interface.

SELECT register

The APSEL field in the SELECT DP selects between the two possible Access Ports. The APBANKSEL is used to specify the most-significant four bits of the register address in the AP that is being accessed.

Only three JLink commands are required and they are all named intuitively:

  1. SWDWriteDP [ADDRESS] [VALUE]
  2. SWDReadAP [ADDRESS]
  3. SWDWriteAP [ADDRESS] [VALUE]

But there are two tricky things to remember:

  1. The ADDRESS parameter is automatically shifted left by two bits; Entering "SWDReadAP 1" is interpreted as "SWDReadAP 100b" or "SWDReadAP 4".
  2. The first invocation of SWDReadAP returns stale data so it is typically called at least twice.

The JLink utility is installed along with the Segger J-Link drivers (on Linux it is named JLinkExe). First plug a DK into the PC and launch the JLink utility. Then select the SWD interface:

J-Link>SWDSelect
	Select SWD by sending SWD switching sequence.
	Found SWD-DP with ID 0x2BA01477

Next the debug interface must be powered up:

J-Link>SWDWriteDP 1 0x50000000
	Write DP register 1 = 0x50000000

Select the CTRL-AP by setting APSEL to 1 and APBANKSEL to 0x00:

SWDWriteDP 2 0x01000000
	Write DP register 2 = 0x01000000

Now start the ERASEALL operation:

SWDWriteAP 1 1
	Write AP register 1 = 0x00000001

And then read ERASEALLSTATUS until it reads 0:

SWDReadAP 2
	Read AP register 2 = 0xXXXXXXXX
SWDReadAP 2
	Read AP register 2 = 0x00000001
SWDReadAP 2
	Read AP register 2 = 0x00000000

The APPROTECTSTATUS register can be read without selecting a different AP:

SWDReadAP 3
	Read AP register 3 = 0xXXXXXXXX
SWDReadAP 3
	Read AP register 3 = 0x00000001

A value of 1 means that access port protection is disabled.

Parents
No Data
Comment
  • $core_v2_ui.GetResizedImageHtml($comment.User.AvatarUrl, 44, 44, "%{border='0px', alt=$comment.User.DisplayName, ResizeMethod='ZoomAndCrop'}")
    $core_v2_ui.UserPresence($comment.User.Id) $comment.User.DisplayName
    This comment is under review.
Children
No Data