From 894117867123d4c5a2f7ddf6bb9dc49db8dfbd4f Mon Sep 17 00:00:00 2001 From: Maximilian Deubel Date: Fri, 15 Mar 2024 13:09:27 +0100 Subject: [PATCH] target/nrf91: add mass_erase and recovery probe Signed-off-by: Maximilian Deubel --- src/target/adiv5.c | 9 ++ src/target/adiv5.h | 1 + src/target/nrf91.c | 280 ++++++++++++++++++++++++++++++++++++-- src/target/target_probe.c | 13 ++ src/target/target_probe.h | 1 + 5 files changed, 294 insertions(+), 10 deletions(-) diff --git a/src/target/adiv5.c b/src/target/adiv5.c index 513d2b37f7b..21bd3e3fc13 100644 --- a/src/target/adiv5.c +++ b/src/target/adiv5.c @@ -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); @@ -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++; diff --git a/src/target/adiv5.h b/src/target/adiv5.h index a39a52b20e5..ba182b6fd0f 100644 --- a/src/target/adiv5.h +++ b/src/target/adiv5.h @@ -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 { diff --git a/src/target/nrf91.c b/src/target/nrf91.c index e14fdfadd07..099004980d1 100644 --- a/src/target/nrf91.c +++ b/src/target/nrf91.c @@ -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 +#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[] = { + 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; + +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 */ @@ -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"); + } + 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; @@ -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__); @@ -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)) { + 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 + 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; +} diff --git a/src/target/target_probe.c b/src/target/target_probe.c index eb44e984b79..72b1bd154a9 100644 --- a/src/target/target_probe.c +++ b/src/target/target_probe.c @@ -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"))); @@ -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) @@ -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. diff --git a/src/target/target_probe.h b/src/target/target_probe.h index 78e89eafad8..97d69865000 100644 --- a/src/target/target_probe.h +++ b/src/target/target_probe.h @@ -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 */