Skip to content

Commit

Permalink
target/nrf91: add mass_erase and recovery probe
Browse files Browse the repository at this point in the history
Signed-off-by: Maximilian Deubel <[email protected]>
  • Loading branch information
maxd-nordic committed Mar 18, 2024
1 parent e52eafa commit 2d81846
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 6 deletions.
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
234 changes: 228 additions & 6 deletions src/target/nrf91.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "target_internal.h"
#include "cortexm.h"
#include "adiv5.h"
#include "gdb_packet.h"

/* Non-Volatile Memory Controller (NVMC) Registers */
#define NRF91_NVMC 0x50039000U
Expand All @@ -15,6 +16,52 @@
#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
#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

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, 300);

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 +96,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");
}
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 +134,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 +148,180 @@ 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,
};

return nrf91_ctrl_ap_mass_erase(&ctrl_ap);
}

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);
}

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:
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);

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
1 change: 1 addition & 0 deletions src/target/target_probe.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,6 @@ bool stm32mp15_cm4_probe(target_s *target);
bool zynq7_probe(target_s *target);

void lpc55_dp_prepare(adiv5_debug_port_s *dp);
bool nrf91_dp_prepare(adiv5_debug_port_s * dp);

#endif /* TARGET_TARGET_PROBE_H */

0 comments on commit 2d81846

Please sign in to comment.