Skip to content

Commit a742340

Browse files
committed
boot: zephyr: add nRF71 MRAMC support to self-RWX lock
Generalize the self-RWX lock for nRF71 and nRF54 series devices. Signed-off-by: Erdem Simsek <erdem.simsek@nordicsemi.no>
1 parent bc0fc51 commit a742340

2 files changed

Lines changed: 113 additions & 76 deletions

File tree

boot/zephyr/Kconfig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,19 +544,24 @@ config NCS_MCUBOOT_DISABLE_SELF_RWX_SUPPORTED
544544
default y if SOC_NRF54LV10A_CPUAPP
545545
default y if SOC_NRF54LM20A_CPUAPP || SOC_NRF54LM20B_CPUAPP
546546
default y if SOC_NRF54LS05B_CPUAPP
547+
default y if SOC_NRF7120_ENGA_CPUAPP
547548

548549
config NCS_MCUBOOT_DISABLE_SELF_RWX
549550
bool "Disable read and execution on self NVM"
550551
depends on NCS_MCUBOOT_DISABLE_SELF_RWX_SUPPORTED && !FPROTECT
551552
help
552-
Sets RRAMC's region no.4 protection before jumping to application.
553-
It disables reads writes and execution memory area which holds MCUBOOT.
553+
Sets the NVM controller region protection before jumping to application.
554+
It disables reads, writes and execution of the memory area which holds
555+
MCUboot. On nRF54L (RRAMC) this uses region no.4. On nRF71 (MRAMC) a single
556+
region covers at most 31 KiB, so regions no.4 and no.5 are chained to cover
557+
the full MCUboot slot.
554558

555559
config NCS_MCUBOOT_DISABLE_SELF_RWX_SKIP_SIZE
556560
hex
557561
depends on NCS_MCUBOOT_DISABLE_SELF_RWX
558562
depends on !PARTITION_MANAGER_ENABLED
559563
default 0x800 if SOC_SERIES_NRF54L
564+
default 0x800 if SOC_SERIES_NRF71
560565
help
561566
Size of region, at the beginning of the partition, that does not require
562567
protections. This is used, for example, when MCUboot type image is protected

boot/zephyr/main.c

Lines changed: 106 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -178,49 +178,118 @@ K_SEM_DEFINE(boot_log_sem, 1, 1);
178178
#define CLEANUP_RAM_GAP_SIZE ((int) (__ramfunc_end - __ramfunc_region_start))
179179

180180
#if defined(CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX)
181-
/* Disabling R_X has to be done while running from RAM for obvious reasons.
182-
* Moreover as a last step before jumping to application it must work even after
183-
* RAM has been cleared, therefore these operations are performed while executing from RAM.
184-
* RAM cleanup ommits portion of the memory where code lives.
181+
/* MCUboot disables read/write/execute on its own NVM region(s) before jumping to
182+
* the application. This has to run from RAM: once EXECUTE (and READ) are cleared
183+
* on the MCUboot slot, any further fetch or read from it faults. It is therefore
184+
* a __ramfunc and is invoked from jump_in() before RAM cleanup, while the MCUboot
185+
* stack is still valid.
186+
*
187+
* nRF54L uses RRAMC, nRF71 uses MRAMC. Both controllers expose an identical
188+
* REGION[n].CONFIG bit layout (READ=0, WRITE=1, EXECUTE=2, LOCK=13, SIZE=16..20)
189+
* and a separate REGION[n].ADDRESS, so only the peripheral base, the register
190+
* field macros and the maximum single-region size differ between them.
191+
*/
192+
#if defined(CONFIG_SOC_SERIES_NRF71)
193+
#include <hal/nrf_mramc.h>
194+
#define NVM_REGION NRF_MRAMC->REGION
195+
#define NVM_CONFIG_READ_Msk MRAMC_REGION_CONFIG_READ_Msk
196+
#define NVM_CONFIG_WRITE_Msk MRAMC_REGION_CONFIG_WRITE_Msk
197+
#define NVM_CONFIG_EXECUTE_Msk MRAMC_REGION_CONFIG_EXECUTE_Msk
198+
#define NVM_CONFIG_LOCK_Msk MRAMC_REGION_CONFIG_LOCK_Msk
199+
#define NVM_CONFIG_SIZE_Msk MRAMC_REGION_CONFIG_SIZE_Msk
200+
#define NVM_CONFIG_SIZE_Pos MRAMC_REGION_CONFIG_SIZE_Pos
201+
/* A single MRAMC region covers at most 31 KiB (5-bit SIZE), so region no.4 and
202+
* no.5 are chained to protect the whole MCUboot slot.
185203
*/
204+
#define NVM_REGION_MAX_SIZE_KB 31
205+
#define NVM_SELF_REGION_COUNT 2
206+
#else
186207
#include <hal/nrf_rramc.h>
187-
188-
#define RRAMC_REGION_RWX_LSB 0
189-
#define RRAMC_REGION_RWX_WIDTH 3
190-
191-
#define RRAMC_REGION_NUMBER 4
192-
#define NRF_RRAM_REGION_SIZE_UNIT 0x400
193-
#define NRF_RRAM_REGION_ADDRESS_RESOLUTION 0x400
194-
208+
#define NVM_REGION NRF_RRAMC->REGION
209+
#define NVM_CONFIG_READ_Msk RRAMC_REGION_CONFIG_READ_Msk
210+
#define NVM_CONFIG_WRITE_Msk RRAMC_REGION_CONFIG_WRITE_Msk
211+
#define NVM_CONFIG_EXECUTE_Msk RRAMC_REGION_CONFIG_EXECUTE_Msk
212+
#define NVM_CONFIG_LOCK_Msk RRAMC_REGION_CONFIG_LOCK_Msk
213+
#define NVM_CONFIG_SIZE_Msk RRAMC_REGION_CONFIG_SIZE_Msk
214+
#define NVM_CONFIG_SIZE_Pos RRAMC_REGION_CONFIG_SIZE_Pos
195215
#if defined(CONFIG_SOC_NRF54L15_CPUAPP) || defined(CONFIG_SOC_NRF54L05_CPUAPP) || \
196216
defined(CONFIG_SOC_NRF54L10_CPUAPP)
197-
#define MAX_PROTECTED_REGION_SIZE (31 * 1024)
217+
#define NVM_REGION_MAX_SIZE_KB 31
198218
#elif defined(CONFIG_SOC_NRF54LV10A_CPUAPP) || defined(CONFIG_SOC_NRF54LM20A_CPUAPP) || \
199219
defined(CONFIG_SOC_NRF54LM20B_CPUAPP)
200-
#define MAX_PROTECTED_REGION_SIZE (127 * 1024)
220+
#define NVM_REGION_MAX_SIZE_KB 127
201221
#elif defined(CONFIG_SOC_NRF54LS05B_CPUAPP)
202-
#define MAX_PROTECTED_REGION_SIZE (1023 * 1024)
222+
#define NVM_REGION_MAX_SIZE_KB 1023
223+
#endif
224+
/* nRF54L fits the whole MCUboot slot in a single region. */
225+
#define NVM_SELF_REGION_COUNT 1
203226
#endif
204227

205-
#define RRAMC_REGION_CONFIG NRF_RRAMC->REGION[RRAMC_REGION_NUMBER].CONFIG
206-
#define RRAMC_REGION_CONFIG_H (((uint32_t)(&(RRAMC_REGION_CONFIG))) >> 16)
207-
#define RRAMC_REGION_CONFIG_L (((uint32_t)(&(RRAMC_REGION_CONFIG))) & 0x0000fffful)
208-
209-
#define RRAMC_REGION_ADDRESS NRF_RRAMC->REGION[RRAMC_REGION_NUMBER].ADDRESS
210-
#define RRAMC_REGION_ADDRESS_H (((uint32_t)(&(RRAMC_REGION_ADDRESS))) >> 16)
211-
#define RRAMC_REGION_ADDRESS_L (((uint32_t)(&(RRAMC_REGION_ADDRESS))) & 0x0000fffful)
228+
/* MCUboot self-lock uses region no.4 first (and no.5 when chained). */
229+
#define NVM_SELF_REGION_FIRST 4
230+
#define NVM_REGION_SIZE_UNIT 0x400
231+
#define NVM_REGION_ADDRESS_RESOLUTION 0x400
212232

213-
BUILD_ASSERT((PROTECTED_REGION_START % NRF_RRAM_REGION_ADDRESS_RESOLUTION) == 0,
233+
BUILD_ASSERT((PROTECTED_REGION_START % NVM_REGION_ADDRESS_RESOLUTION) == 0,
214234
"Start of protected region is not aligned - not possible to protect");
215235

216-
BUILD_ASSERT((PROTECTED_REGION_SIZE % NRF_RRAM_REGION_SIZE_UNIT) == 0,
236+
BUILD_ASSERT((PROTECTED_REGION_SIZE % NVM_REGION_SIZE_UNIT) == 0,
217237
"Size of protected region is not aligned - not possible to protect");
218238

219-
BUILD_ASSERT(PROTECTED_REGION_SIZE <= MAX_PROTECTED_REGION_SIZE,
220-
"Size of protected region is too big for protection");
239+
BUILD_ASSERT(PROTECTED_REGION_SIZE <=
240+
(NVM_SELF_REGION_COUNT * NVM_REGION_MAX_SIZE_KB * 1024),
241+
"Size of protected region is too big for protection");
221242

222-
#define PROTECTED_REGION_START_H ((PROTECTED_REGION_START) >> 16)
223-
#define PROTECTED_REGION_START_L ((PROTECTED_REGION_START) & 0x0000fffful)
243+
/* Lock one NVM region read/write/execute-disabled. Only compile-time-constant
244+
* register addresses and masks are used, so no read of the (about to be locked)
245+
* MCUboot flash is emitted.
246+
*/
247+
static void __ramfunc nvm_region_lock(uint32_t region, uint32_t start, uint32_t size_kb)
248+
{
249+
volatile uint32_t *config = &NVM_REGION[region].CONFIG;
250+
volatile uint32_t *address = &NVM_REGION[region].ADDRESS;
251+
uint32_t cfg = *config;
252+
253+
/* If a previous stage (NSIB) already provisioned the region the SIZE
254+
* field is non-zero; otherwise set the address and size ourselves. The
255+
* address register is left untouched when already configured, as the
256+
* region may already be locked.
257+
*/
258+
if ((cfg & NVM_CONFIG_SIZE_Msk) == 0) {
259+
cfg &= ~NVM_CONFIG_SIZE_Msk;
260+
cfg |= (size_kb << NVM_CONFIG_SIZE_Pos) & NVM_CONFIG_SIZE_Msk;
261+
*address = start;
262+
__DSB();
263+
}
264+
265+
/* Clear read/write/execute and lock against re-enabling. A locked region
266+
* can still be made stricter, which is what happens here.
267+
*/
268+
cfg &= ~(NVM_CONFIG_READ_Msk | NVM_CONFIG_WRITE_Msk | NVM_CONFIG_EXECUTE_Msk);
269+
cfg |= NVM_CONFIG_LOCK_Msk;
270+
*config = cfg;
271+
__DSB();
272+
}
273+
274+
/* Lock the whole MCUboot slot, chaining consecutive regions when a single one
275+
* cannot cover it (nRF71). nRF54L fits in one region and loops once.
276+
*/
277+
static void __ramfunc disable_self_rwx(void)
278+
{
279+
uint32_t remaining_kb = PROTECTED_REGION_SIZE / NVM_REGION_SIZE_UNIT;
280+
uint32_t start = PROTECTED_REGION_START;
281+
uint32_t region = NVM_SELF_REGION_FIRST;
282+
283+
while (remaining_kb) {
284+
uint32_t chunk_kb = (remaining_kb < NVM_REGION_MAX_SIZE_KB) ?
285+
remaining_kb : NVM_REGION_MAX_SIZE_KB;
286+
287+
nvm_region_lock(region, start, chunk_kb);
288+
start += chunk_kb * NVM_REGION_SIZE_UNIT;
289+
remaining_kb -= chunk_kb;
290+
region++;
291+
}
292+
}
224293

225294
#endif /* CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX */
226295

@@ -276,6 +345,15 @@ static void __ramfunc jump_in(struct arm_vector_table *vt)
276345
}
277346
#endif
278347

348+
#if defined(CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX)
349+
/* Lock MCUboot's own NVM here, from RAM and while still on the MCUboot
350+
* stack (i.e. before any RAM cleanup). Everything executed afterwards
351+
* lives in RAM (__ramfunc), so the now non-readable/executable MCUboot
352+
* slot is never fetched or read again.
353+
*/
354+
disable_self_rwx();
355+
#endif
356+
279357
#ifdef CONFIG_CPU_CORTEX_M
280358
__set_MSP(vt->msp);
281359
#endif
@@ -335,39 +413,6 @@ static void __ramfunc jump_in(struct arm_vector_table *vt)
335413
#endif /* CONFIG_MCUBOOT_INFINITE_LOOP_AFTER_RAM_CLEANUP */
336414
#endif /* CONFIG_MCUBOOT_CLEANUP_RAM */
337415

338-
#ifdef CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX
339-
".thumb_func\n"
340-
"region_disable_rwx:\n"
341-
" movw r1, %6\n"
342-
" movt r1, %7\n"
343-
" ldr r2, [r1]\n"
344-
/* Size of the region should be set at this point
345-
* by NSIB's DISABLE_NEXT_W.
346-
* If not, the region has not been configured yet.
347-
* Set the size and address to the partition size and address.
348-
*/
349-
" ldr r5, =%12\n"
350-
" ands r4, r2, r5\n"
351-
" cbnz r4, clear_rwx\n"
352-
/* Set the size of the protected region */
353-
" movt r2, %8\n"
354-
/* Set the address of the protected region */
355-
" movw r5, %13\n"
356-
" movt r5, %14\n"
357-
" movw r6, %15\n"
358-
" movt r6, %16\n"
359-
" str r6, [r5]\n"
360-
" dsb\n"
361-
"clear_rwx:\n"
362-
" bfc r2, %9, %10\n"
363-
/* Disallow further modifications */
364-
" orr r2, %11\n"
365-
" str r2, [r1]\n"
366-
" dsb\n"
367-
/* Next assembly line is important for current function */
368-
369-
#endif /* CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX */
370-
371416
/* Jump to reset vector of an app */
372417
" bx r0\n"
373418
:
@@ -377,19 +422,6 @@ static void __ramfunc jump_in(struct arm_vector_table *vt)
377422
"r" (CLEANUP_RAM_GAP_START),
378423
"r" (CLEANUP_RAM_GAP_SIZE),
379424
"i" (0)
380-
#ifdef CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX
381-
, "i" (RRAMC_REGION_CONFIG_L),
382-
"i" (RRAMC_REGION_CONFIG_H),
383-
"i" ((PROTECTED_REGION_SIZE) / (NRF_RRAM_REGION_SIZE_UNIT)),
384-
"i" (RRAMC_REGION_RWX_LSB),
385-
"i" (RRAMC_REGION_RWX_WIDTH),
386-
"i" (RRAMC_REGION_CONFIG_LOCK_Msk),
387-
"i" (RRAMC_REGION_CONFIG_SIZE_Msk),
388-
"i" (RRAMC_REGION_ADDRESS_L),
389-
"i" (RRAMC_REGION_ADDRESS_H),
390-
"i" (PROTECTED_REGION_START_L),
391-
"i" (PROTECTED_REGION_START_H)
392-
#endif /* CONFIG_NCS_MCUBOOT_DISABLE_SELF_RWX */
393425
: "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory"
394426
);
395427
}

0 commit comments

Comments
 (0)