Skip to content
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

target/nrf91: add mass_erase and recovery probe #1785

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/target/adiv5.c
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,8 @@ void adiv5_dp_init(adiv5_debug_port_s *const dp)

dp->target_partno = (targetid & ADIV5_DP_TARGETID_TPARTNO_MASK) >> ADIV5_DP_TARGETID_TPARTNO_OFFSET;

dp->target_revision = (targetid & ADIV5_DP_TARGETID_TREVISION_MASK) >> ADIV5_DP_TARGETID_TREVISION_OFFSET;

DEBUG_INFO("TARGETID 0x%08" PRIx32 " designer 0x%x partno 0x%x\n", targetid, dp->target_designer_code,
dp->target_partno);

Expand Down Expand Up @@ -1003,6 +1005,13 @@ void adiv5_dp_init(adiv5_debug_port_s *const dp)
if (dp->target_designer_code == JEP106_MANUFACTURER_NXP)
lpc55_dp_prepare(dp);

if (dp->target_designer_code == JEP106_MANUFACTURER_NORDIC && dp->target_partno == 0x90U) {
if (!nrf91_dp_prepare(dp)) {
/* device is in secure state, only show rescue target */
return;
}
}

/* Probe for APs on this DP */
size_t invalid_aps = 0;
dp->refcnt++;
Expand Down
1 change: 1 addition & 0 deletions src/target/adiv5.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ struct adiv5_debug_port {
/* TARGETID designer and partno, present on DPv2 */
uint16_t target_designer_code;
uint16_t target_partno;
uint8_t target_revision;
};

struct adiv5_access_port {
Expand Down
280 changes: 270 additions & 10 deletions src/target/nrf91.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,75 @@
#include "target_internal.h"
#include "cortexm.h"
#include "adiv5.h"
#include "gdb_packet.h"

/* Non-Volatile Memory Controller (NVMC) Registers */
#define NRF91_NVMC 0x50039000U
#define NRF91_NVMC_READY (NRF91_NVMC + 0x400U)
#define NRF91_NVMC_CONFIG (NRF91_NVMC + 0x504U)
#define NRF91_NVMC_ERASEALL (NRF91_NVMC + 0x50cU)
#define NRF91_NVMC 0x50039000U
#define NRF91_NVMC_READY (NRF91_NVMC + 0x400U)
#define NRF91_NVMC_READYNEXT (NRF91_NVMC + 0x408U)
#define NRF91_NVMC_CONFIG (NRF91_NVMC + 0x504U)
#define NRF91_NVMC_ERASEALL (NRF91_NVMC + 0x50cU)

#define NVMC_TIMEOUT_MS 300U

#define NRF91_NVMC_CONFIG_REN 0x0U // Read only access
#define NRF91_NVMC_CONFIG_WEN 0x1U // Write enable
#define NRF91_NVMC_CONFIG_EEN 0x2U // Erase enable
#define NRF91_NVMC_CONFIG_PEEN 0x3U // Partial erase enable

/* https://infocenter.nordicsemi.com/topic/ps_nrf9160/dif.html */
#define NRF91_PARTNO 0x90U

#define NRF91_CTRL_AP_RESET ADIV5_AP_REG(0x000)
#define NRF91_CTRL_AP_ERASEALL ADIV5_AP_REG(0x004)
#define NRF91_CTRL_IDR_EXPECTED 0x12880000
#define NRF91_AHB_AP_IDR_EXPECTED 0x84770001
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

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

These are missing their U suffixes to ensure they're defined as unsigned values. Because the AP IDR value (as an example) sets the sign bit on 32-bit platforms, if this value is then widened for any reason, then the upper bytes of the new value will be all set high, and if it is narrowed, this invokes UB (due to changing the value of the sign bit). Neither of these problems occur with unsigned constants.

#define NRF91_CTRL_AP_ERASEALLSTATUS ADIV5_AP_REG(0x008)

/* https://infocenter.nordicsemi.com/topic/ps_nrf9161/uicr.html */
#define NRF91_UICR_APPROTECT 0x00FF8000U
#define NRF91_UICR_SECUREAPPROTECT 0x00FF802CU
#define NRF91_UICR_APPROTECT_UNPROTECT_VAL 0x50FA50FAU
#define NRF91_UICR_ERASED_VAL 0xFFFFFFFFU

unsigned char empty_app[] = {
Copy link
Member

Choose a reason for hiding this comment

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

Please do not use raw types. uint8_t if you want to make this unsigned (and then U-suffix every value in the array initialisation so they're unsigned and won't get sign extended).

0x00, 0x10, 0x00, 0x20, 0x09, 0x00, 0x00, 0x00, 0x05, 0x4b, 0x4f, 0xf0,
0x5a, 0x02, 0xc3, 0xf8, 0x10, 0x2e, 0x03, 0x4b, 0x4f, 0xf0, 0x5a, 0x02,
0xc3, 0xf8, 0x00, 0x2e, 0xfe, 0xe7, 0x00, 0x00, 0x00, 0x90, 0x03, 0x50
};
unsigned int empty_app_len = 36;
Copy link
Member

Choose a reason for hiding this comment

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

Please use ARRAY_LENGTH() and make this a #define. This is not a constant expression in C and so uses Flash space unnecessarily to store a full variable. something like #define NRF91_EMPTY_APP_LEN ARRAY_LENGTH(empty_app) would be appropriate.


static bool nrf91_ctrl_ap_mass_erase(adiv5_access_port_s *ap)
{
adiv5_ap_write(ap, NRF91_CTRL_AP_ERASEALL, 1);
platform_timeout_s timeout;
platform_timeout_set(&timeout, NVMC_TIMEOUT_MS);

bool ret = false;

while (true) {
uint32_t status = adiv5_ap_read(ap, NRF91_CTRL_AP_ERASEALLSTATUS);
if (status == 0) {
ret = true;
DEBUG_INFO("nRF91 mass erase succeeded.\n");
break;
}
if (platform_timeout_is_expired(&timeout)) {
DEBUG_INFO("nRF91 mass erase failed.\n");
break;
}
}

platform_delay(10);

adiv5_ap_write(ap, NRF91_CTRL_AP_RESET, 1);
adiv5_ap_write(ap, NRF91_CTRL_AP_RESET, 0);

platform_delay(200);

return ret;
}

static bool nrf91_wait_ready(target_s *const target, platform_timeout_s *const timeout)
{
/* Poll for NVMC_READY */
Expand Down Expand Up @@ -49,6 +106,25 @@ static bool nrf91_flash_erase(target_flash_s *flash, target_addr_t addr, size_t
return nrf91_wait_ready(target, NULL);
}

static bool nrf91_uicr_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len)
{
target_s *target = flash->t;

bool erase_needed = false;

for (size_t offset = 0; offset < len; offset += 4) {
if (target_mem_read32(target, addr + offset) != NRF91_UICR_ERASED_VAL) {
erase_needed = true;
break;
}
}

if (erase_needed) {
gdb_out("Skipping UICR erase, mass erase might be needed\n");
}
Comment on lines +122 to +124
Copy link
Member

Choose a reason for hiding this comment

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

The braces are superfluous, please drop them.

return true;
}

static bool nrf91_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len)
{
target_s *target = flash->t;
Expand All @@ -68,6 +144,7 @@ static bool nrf91_flash_write(target_flash_s *flash, target_addr_t dest, const v

static void nrf91_add_flash(target_s *target, uint32_t addr, size_t length, size_t erasesize)
{
/* add main flash */
target_flash_s *flash = calloc(1, sizeof(*flash));
if (!flash) { /* calloc failed: heap exhaustion */
DEBUG_WARN("calloc: failed in %s\n", __func__);
Expand All @@ -81,25 +158,208 @@ static void nrf91_add_flash(target_s *target, uint32_t addr, size_t length, size
flash->write = nrf91_flash_write;
flash->erased = 0xff;
target_add_flash(target, flash);

/* add separate UICR flash */
target_flash_s *flash_uicr = calloc(1, sizeof(*flash_uicr));
if (!flash_uicr) { /* calloc failed: heap exhaustion */
DEBUG_WARN("calloc: failed in %s\n", __func__);
return;
}

flash_uicr->start = 0xff8000U;
flash_uicr->length = 0x1000U;
flash_uicr->blocksize = 0x4U;
flash_uicr->erase = nrf91_uicr_flash_erase;
flash_uicr->write = nrf91_flash_write;
flash_uicr->erased = 0xff;
target_add_flash(target, flash_uicr);
}

static bool nrf91_mass_erase(target_s *target)
{
adiv5_access_port_s *ap = cortex_ap(target);
adiv5_access_port_s ctrl_ap = {
.dp = ap->dp,
.apsel = 0x4U,
};

if (!nrf91_ctrl_ap_mass_erase(&ctrl_ap)) {
Copy link
Member

Choose a reason for hiding this comment

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

Please drop these braces as they are unnecessary. The code base style is for single statement bodies of conditional block to not be enclosed in braces.

return false;
}

if (ap->dp->target_revision > 2) {
target_mem_write32(target, NRF91_NVMC_CONFIG, NRF91_NVMC_CONFIG_WEN);
while (target_mem_read32(target, NRF91_NVMC_READY) == 0) {
platform_delay(1);
DEBUG_INFO("Waiting for NVMC to become ready\n");
}

target_mem_write(target, 0, empty_app, empty_app_len);
target_mem_write32(target, NRF91_UICR_APPROTECT, NRF91_UICR_APPROTECT_UNPROTECT_VAL);
target_mem_write32(target, NRF91_UICR_SECUREAPPROTECT, NRF91_UICR_APPROTECT_UNPROTECT_VAL);

while (target_mem_read32(target, NRF91_NVMC_READY) == 0) {
platform_delay(1);
DEBUG_INFO("Waiting for NVMC to become ready\n");
}

target_mem_write32(target, NRF91_NVMC_CONFIG, NRF91_NVMC_CONFIG_REN);
}

return true;
}

static bool nrf91_exit_flash_mode(target_s *const target)
{
adiv5_access_port_s *ap = cortex_ap(target);
/* Persist AP access if uninitialized (only needed for devices with hardenend APPROTECT) */
if (ap->dp->target_revision > 2) {
bool approtect_erased = target_mem_read32(target, NRF91_UICR_APPROTECT) == NRF91_UICR_ERASED_VAL;
bool secureapprotect_erased = target_mem_read32(target, NRF91_UICR_SECUREAPPROTECT) == NRF91_UICR_ERASED_VAL;

target_mem_write32(target, NRF91_NVMC_CONFIG, NRF91_NVMC_CONFIG_WEN);

while (target_mem_read32(target, NRF91_NVMC_READY) == 0) {
platform_delay(1);
DEBUG_INFO("Waiting for NVMC to become ready\n");
}

if (approtect_erased) {
target_mem_write32(target, NRF91_UICR_APPROTECT, NRF91_UICR_APPROTECT_UNPROTECT_VAL);
}
if (secureapprotect_erased) {
target_mem_write32(target, NRF91_UICR_SECUREAPPROTECT, NRF91_UICR_APPROTECT_UNPROTECT_VAL);
}

while (target_mem_read32(target, NRF91_NVMC_READY) == 0) {
platform_delay(1);
DEBUG_INFO("Waiting for NVMC to become ready\n");
}

target_mem_write32(target, NRF91_NVMC_CONFIG, NRF91_NVMC_CONFIG_REN);
}
return true;
}

bool nrf91_probe(target_s *target)
{
adiv5_access_port_s *ap = cortex_ap(target);

if (ap->dp->version < 2U)
if (ap->dp->version < 2U || ap->dp->target_partno != NRF91_PARTNO)
return false;

switch (ap->dp->target_partno) {
case 0x90:
#ifndef ENABLE_DEBUG
Copy link
Member

Choose a reason for hiding this comment

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

This wants to be #ifndef DEBUG_INFO_IS_NOOP to correctly guard these memory read-backs.

uint32_t partno = target_mem_read32(target, 0x00FF0140);
uint32_t hwrevision = target_mem_read32(target, 0x00FF0144);
uint32_t variant = target_mem_read32(target, 0x00FF0148);
DEBUG_INFO("nRF%04" PRIx32 " %4s%4s detected!\n", partno, (const char *)&variant, (const char *)&hwrevision);
#endif
switch (ap->dp->target_revision) {
case 0:
case 1:
case 2:
target->driver = "Nordic nRF9160";
target->target_options |= TOPT_INHIBIT_NRST;
target_add_ram(target, 0x20000000, 256U * 1024U);
nrf91_add_flash(target, 0, 4096U * 256U, 4096U);
break;
case 3:
target->driver = "Nordic nRF91x1";
break;
default:
target->driver = "Nordic nRF91";
}

target->target_options |= TOPT_INHIBIT_NRST;
target_add_ram(target, 0x20000000, 256U * 1024U);
nrf91_add_flash(target, 0, 4096U * 256U, 4096U);

target->mass_erase = nrf91_mass_erase;
target->exit_flash_mode = nrf91_exit_flash_mode;

return true;
}

static bool nrf91_rescue_do_recover(target_s *target)
{
adiv5_access_port_s *ap = (adiv5_access_port_s *)target->priv;

const bool hardened_approtect = ap->dp->target_revision > 2;

/* on some revisions, this needs to be repeated */
for (size_t i = 0; i < 3; ++i) {
if (!nrf91_ctrl_ap_mass_erase(ap))
continue;
if (!hardened_approtect) {
/* pin reset is needed on older devices */
platform_nrst_set_val(true);
platform_delay(100);
platform_nrst_set_val(false);

/* repetition not needed and debug port inactive at this point */
return false;
}

//check if CSW DEVICEEN is set
struct adiv5_access_port ahb_ap = *ap;
ahb_ap.apsel = 0x0U;
const uint32_t csw = ap->dp->ap_read(&ahb_ap, ADIV5_AP_CSW);
if (csw & ADIV5_AP_CSW_DEVICEEN) {
DEBUG_INFO("nRF91 Rescue succeeded.\n");
break;
}
}

return false;
}

bool nrf91_rescue_probe(adiv5_access_port_s *ap)
{
target_s *target = target_new();
if (!target) {
return false;
}
adiv5_ap_ref(ap);
target->attach = (void *)nrf91_rescue_do_recover;
target->priv = ap;
target->priv_free = (void *)adiv5_ap_unref;
target->driver = "nRF91 Rescue (Attach, then scan again!)";

return true;
}

/* check if nRF91 target is in secure state, return false if device is protected */
bool nrf91_dp_prepare(adiv5_debug_port_s *const dp)
{
adiv5_access_port_s ahb_ap = {
.dp = dp,
.apsel = 0x0U,
};
adiv5_access_port_s ctrl_ap = {
.dp = dp,
.apsel = 0x4U,
};
ahb_ap.idr = adiv5_ap_read(&ahb_ap, ADIV5_AP_IDR);
ahb_ap.csw = adiv5_ap_read(&ahb_ap, ADIV5_AP_CSW);
ctrl_ap.idr = adiv5_ap_read(&ctrl_ap, ADIV5_AP_IDR);

if (ahb_ap.idr != NRF91_AHB_AP_IDR_EXPECTED) {
DEBUG_ERROR(
"nRF91: AHB-AP IDR is 0x%08" PRIx32 ", expected 0x%08" PRIx32 "\n", ahb_ap.idr, NRF91_AHB_AP_IDR_EXPECTED);
}

if (ctrl_ap.idr != NRF91_CTRL_IDR_EXPECTED) {
DEBUG_ERROR(
"nRF91: CTRL-AP IDR is 0x%08" PRIx32 ", expected 0x%08" PRIx32 "\n", ctrl_ap.idr, NRF91_CTRL_IDR_EXPECTED);
}

if (!(ahb_ap.csw & ADIV5_AP_CSW_DEVICEEN)) {
DEBUG_INFO("nRF91 is in secure state, creating rescue target\n");
adiv5_access_port_s *ap = calloc(1, sizeof(*ap));
if (!ap) { /* calloc failed: heap exhaustion */
DEBUG_ERROR("calloc: failed in %s\n", __func__);
return false;
}
memcpy(ap, &ctrl_ap, sizeof(*ap));
nrf91_rescue_probe(ap);
return false;
}
return true;
}
13 changes: 13 additions & 0 deletions src/target/target_probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
{ \
lpc55_dp_prepare_nop(debug_port); \
};
#define NRF91_DP_PREPARE_WEAK_NOP(name) \
__attribute__((weak)) bool name(adiv5_debug_port_s *const debug_port) \
{ \
return nrf91_dp_prepare_nop(debug_port); \
};
#else
#define CORTEXAR_PROBE_WEAK_NOP(name) \
extern bool name(adiv5_access_port_s *, target_addr_t) __attribute__((weak, alias("cortexar_probe_nop")));
Expand All @@ -56,6 +61,8 @@
#define TARGET_PROBE_WEAK_NOP(name) extern bool name(target_s *) __attribute__((weak, alias("target_probe_nop")));
#define LPC55_DP_PREPARE_WEAK_NOP(name) \
extern void name(adiv5_debug_port_s *) __attribute__((weak, alias("lpc55_dp_prepare_nop")));
#define NRF91_DP_PREPARE_WEAK_NOP(name) \
extern bool name(adiv5_debug_port_s *) __attribute__((weak, alias("nrf91_dp_prepare_nop")));
#endif

static inline bool cortexar_probe_nop(adiv5_access_port_s *const access_port, const target_addr_t base_address)
Expand All @@ -82,6 +89,12 @@ static inline void lpc55_dp_prepare_nop(adiv5_debug_port_s *const debug_port)
(void)debug_port;
}

static inline bool nrf91_dp_prepare_nop(adiv5_debug_port_s *const debug_port)
{
(void)debug_port;
return true;
}

/*
* nop alias functions to allow support for target probe methods
* to be disabled by not compiling/linking them in.
Expand Down
Loading
Loading