Skip to content

Commit acf5d75

Browse files
ahasztagtomchy
authored andcommitted
boot: Adjustments to region protection code for all nRF54L platforms
This commit fixes a couple of issues regarding B0 and MCUBoot region protection for nRF54L. It also adds some tests verifying that the issues are no longer present. Also, support for region and BOOTCONF protection is added for nRF54LM20 and nRF54LV10 platforms. Note: due nRF54LM20 and nRF54LV10 devices being shipped in the TEST mode, the BOOTCONF configuration is not copied to the appropriate REGION[n].CONFIG register and is not automatically applied at startup. Thus, the BOOTCONF configuration does not work with the shipped devices. However, all the code needed for that is present. Signed-off-by: Artur Hadasz <[email protected]>
1 parent 4ba7434 commit acf5d75

File tree

14 files changed

+294
-45
lines changed

14 files changed

+294
-45
lines changed

cmake/sysbuild/bootconf.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function(setup_bootconf_data)
1515
${ZEPHYR_NRF_MODULE_DIR}/scripts/reglock.py
1616
--output ${CMAKE_BINARY_DIR}/bootconf.hex
1717
--size $<TARGET_PROPERTY:partition_manager,PM_B0_SIZE>
18+
--soc ${SB_CONFIG_SOC}
1819
DEPENDS ${APPLICATION_BINARY_DIR}/pm.config
1920
VERBATIM
2021
)

scripts/reglock.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,54 @@
2222
import argparse
2323
import sys
2424
import warnings
25-
26-
SIZE_MAX_KB = 31
25+
import struct
2726

2827
BOOTCONF_ADDR = 0x00FFD080
2928

3029
READ_ALLOWED = 0x01
3130
WRITE_ALLOWED = 0x02
3231
EXECUTE_ALLOWED = 0x04
3332
SECURE = 0x08
34-
OWNER_NONE = 0x00
35-
OWNER_APP = 0x10
36-
OWNER_KMU = 0x20
3733
WRITEONCE = 0x10
38-
LOCK = 0x20
34+
LOCK = 0x2000
35+
36+
SIZE_OFFSET = 16
37+
38+
39+
supported_socs = [
40+
"nrf54l05",
41+
"nrf54l10",
42+
"nrf54l15",
43+
"nrf54lm20a",
44+
"nrf54lv10a",
45+
"nrf54ls05b",
46+
]
47+
48+
def get_max_size_kb(soc):
49+
if soc in ["nrf54l05", "nrf54l10", "nrf54l15"]:
50+
return 31
51+
elif soc in ["nrf54lm20a", "nrf54lv10a"]:
52+
return 127
53+
elif soc in ["nrf54ls05b"]:
54+
return 1023
55+
else:
56+
sys.exit("error: unsupported SoC")
57+
58+
def get_bootconf_reg_32bit_value(soc, size):
59+
value = READ_ALLOWED | EXECUTE_ALLOWED | LOCK
60+
61+
if soc not in ["nrf54ls05b"]:
62+
value |= SECURE
63+
64+
size_kb = size // 1024
65+
max_size_kb = get_max_size_kb(soc)
66+
67+
if size_kb > max_size_kb:
68+
warnings.warn("warning: requested size too big; Setting to allowed maximum")
69+
size_kb = max_size_kb
70+
71+
value |= size_kb << SIZE_OFFSET
72+
return value
3973

4074
def parse_args():
4175
parser = argparse.ArgumentParser(
@@ -48,6 +82,9 @@ def parse_args():
4882
parser.add_argument("-s", "--size", required=False, default="0x7C00",
4983
type=lambda x: hex(int(x, 0)),
5084
help="Size to lock.")
85+
parser.add_argument("--soc", required=True,
86+
type=str,
87+
help="SoC for which to generate the bootconf.")
5188
return parser.parse_args()
5289

5390

@@ -56,17 +93,12 @@ def main():
5693
size = int(args.size, 16)
5794
if size % 1024:
5895
sys.exit("error: requested size not aligned to 1k")
59-
size = size // 1024
60-
if size > SIZE_MAX_KB:
61-
warnings.warn("warning: requested size too big; Setting to allowed maximum")
62-
size = SIZE_MAX_KB
63-
64-
payload = bytearray([
65-
READ_ALLOWED | EXECUTE_ALLOWED | SECURE | OWNER_NONE,
66-
LOCK,
67-
size,
68-
0x0
69-
])
96+
if args.soc not in supported_socs:
97+
sys.exit("error: unsupported SoC")
98+
99+
reg_value = get_bootconf_reg_32bit_value(args.soc, size)
100+
101+
payload = struct.pack('<I', reg_value)
70102

71103
h = IntelHex()
72104
h.frombytes(bytes=payload, offset=BOOTCONF_ADDR)

subsys/bootloader/Kconfig

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,26 @@ config SB_INFINITE_LOOP_AFTER_RAM_CLEANUP
112112
Verification option that keeps execution in infinite loop after
113113
RAM cleanup has been performed.
114114

115+
config SB_DISABLE_SELF_RWX_SUPPORTED
116+
bool
117+
default y if SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP
118+
default y if SOC_NRF54LV10A_ENGA_CPUAPP
119+
default y if SOC_NRF54LM20A_ENGA_CPUAPP
120+
115121
config SB_DISABLE_SELF_RWX
116122
bool "Disable read and execution on self NVM"
117-
depends on (SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP || SOC_NRF54LV10A_ENGA_CPUAPP) && !FPROTECT_ALLOW_COMBINED_REGIONS
123+
depends on SB_DISABLE_SELF_RWX_SUPPORTED && !FPROTECT_ALLOW_COMBINED_REGIONS
118124
help
119125
Sets RRAMC's BOOTCONF region protection before jumping to application.
120126
It disables reads writes and execution memory area which holds NSIB.
121127

122128
config SB_DISABLE_NEXT_W
123129
bool "Disable writes for next stage"
124-
depends on (SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP) && !FPROTECT
130+
depends on SB_DISABLE_SELF_RWX_SUPPORTED && !FPROTECT
125131
help
126132
NSIB disables writes on next stage in bootloading chain.
127-
It uses RRAMC's region 4 and is limited to 31KB.
133+
It uses RRAMC's region 4 and is limited to 31KB for nRF54L15, nRF54L10 and nRF54L05
134+
and to 127KB for nRF54LV10a and nRF54LM20a.
128135

129136
endif # IS_SECURE_BOOTLOADER
130137

subsys/bootloader/bl_boot/bl_boot.c

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,70 @@
2828
#define RRAMC_REGION_FOR_NEXT_W 4
2929
#define NRF_RRAM_REGION_SIZE_UNIT 0x400
3030
#define NRF_RRAM_REGION_ADDRESS_RESOLUTION 0x400
31-
#define NEXT_W_SIZE_KB (PM_MCUBOOT_SIZE / NRF_RRAM_REGION_SIZE_UNIT)
3231

33-
BUILD_ASSERT((PM_MCUBOOT_ADDRESS % NRF_RRAM_REGION_ADDRESS_RESOLUTION) == 0,
34-
"Start of protected region is not aligned");
32+
#if defined(CONFIG_SOC_NRF54L15_CPUAPP) || defined(CONFIG_SOC_NRF54L05_CPUAPP) || \
33+
defined(CONFIG_SOC_NRF54L10_CPUAPP)
34+
#define MAX_NEXT_W_SIZE (31 * 1024)
35+
#elif defined(CONFIG_SOC_NRF54LV10A_ENGA_CPUAPP) || defined(CONFIG_SOC_NRF54LM20A_ENGA_CPUAPP)
36+
#define MAX_NEXT_W_SIZE (127 * 1024)
37+
#elif defined(CONFIG_SOC_NRF54LS05B_ENGA_CPUAPP)
38+
#define MAX_NEXT_W_SIZE (1023 * 1024)
39+
#endif
40+
41+
BUILD_ASSERT((PM_S0_IMAGE_ADDRESS % NRF_RRAM_REGION_ADDRESS_RESOLUTION) == 0,
42+
"Start of S0 image region is not aligned - not possible to protect");
43+
44+
BUILD_ASSERT((PM_S0_IMAGE_SIZE % NRF_RRAM_REGION_SIZE_UNIT) == 0,
45+
"Size of S0 image region is not aligned - not possible to protect");
46+
47+
BUILD_ASSERT(PM_S0_IMAGE_SIZE <= MAX_NEXT_W_SIZE, "Size of S0 partition is too big for protection");
48+
49+
#if defined(PM_S1_IMAGE_ADDRESS)
50+
BUILD_ASSERT((PM_S1_IMAGE_ADDRESS % NRF_RRAM_REGION_ADDRESS_RESOLUTION) == 0,
51+
"Start of S1 image region is not aligned - not possible to protect");
3552

36-
BUILD_ASSERT((PM_MCUBOOT_SIZE % NRF_RRAM_REGION_SIZE_UNIT) == 0,
37-
"Size of protected region is not aligned");
53+
BUILD_ASSERT((PM_S1_IMAGE_SIZE % NRF_RRAM_REGION_SIZE_UNIT) == 0,
54+
"Size of S1 image region is not aligned - not possible to protect");
3855

39-
BUILD_ASSERT(NEXT_W_SIZE_KB < 31,
40-
"Size of requested protection is too big");
56+
BUILD_ASSERT(PM_S1_IMAGE_SIZE <= MAX_NEXT_W_SIZE, "Size of S1 partition is too big for protection");
57+
#endif /* defined(PM_S1_IMAGE_ADDRESS) */
4158

42-
static int disable_next_w(void)
59+
static int disable_next_w(const uint32_t address)
4360
{
61+
uint32_t region_size_kb = 0;
62+
63+
/* Note: the protection is only applied to the image itself, not the header (pad).
64+
* When building with MCUBoot, applying protection to the header is not needed, as the
65+
* header is only used during DFU and is only left for compatibility. Without MCUBoot, the
66+
* header is not present.
67+
*/
68+
if (address == PM_S0_IMAGE_ADDRESS) {
69+
region_size_kb = PM_S0_IMAGE_SIZE / NRF_RRAM_REGION_SIZE_UNIT;
70+
} else if (address == PM_S1_IMAGE_ADDRESS) {
71+
region_size_kb = PM_S1_IMAGE_SIZE / NRF_RRAM_REGION_SIZE_UNIT;
72+
} else {
73+
return -EINVAL;
74+
}
75+
4476
nrf_rramc_region_config_t config = {
45-
.address = PM_MCUBOOT_ADDRESS,
77+
.address = address,
4678
.permissions = NRF_RRAMC_REGION_PERM_READ_MASK |
4779
NRF_RRAMC_REGION_PERM_EXECUTE_MASK,
4880
.writeonce = false,
49-
.lock = false,
50-
.size_kb = NEXT_W_SIZE_KB,
81+
/* There are no issues with locking the region here,
82+
* as the next stage can still impose more strict
83+
* protection by writing 0 to R/X disable bits.
84+
*/
85+
.lock = true,
86+
.size_kb = region_size_kb,
5187
};
5288

5389
nrf_rramc_region_config_set(NRF_RRAMC, RRAMC_REGION_FOR_NEXT_W, &config);
5490
nrf_rramc_region_config_get(NRF_RRAMC, RRAMC_REGION_FOR_NEXT_W, &config);
5591
if (config.permissions & (NRF_RRAMC_REGION_PERM_WRITE_MASK)) {
5692
return -ENOSPC;
5793
}
58-
if (config.size_kb != NEXT_W_SIZE_KB) {
94+
if (config.size_kb != region_size_kb) {
5995
return -ENOSPC;
6096
}
6197

@@ -286,8 +322,8 @@ void bl_boot(const struct fw_info *fw_info)
286322
uint32_t *vector_table = (uint32_t *)fw_info->address;
287323

288324
#if defined(CONFIG_SB_DISABLE_NEXT_W)
289-
if (disable_next_w()) {
290-
printk("Unable to disable writes on next stage.");
325+
if (disable_next_w(fw_info->address)) {
326+
printk("Unable to disable writes on next stage");
291327
return;
292328
}
293329
#endif

sysbuild/Kconfig.secureboot

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,15 @@ config SECURE_BOOT_PUBLIC_KEY_FILES
336336
empty string then only the public key hash corresponding to the private signing key used
337337
to sign the image is included in provision.hex.
338338

339+
config SECURE_BOOT_BOOTCONF_LOCK_WRITES_SUPPORTED
340+
bool
341+
default y if SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP
342+
default y if SOC_NRF54LV10A_ENGA_CPUAPP
343+
default y if SOC_NRF54LM20A_ENGA_CPUAPP
344+
339345
config SECURE_BOOT_BOOTCONF_LOCK_WRITES
340346
bool "Protect bootloader's NVM from writes"
341-
depends on SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP
347+
depends on SECURE_BOOT_BOOTCONF_LOCK_WRITES_SUPPORTED
342348
default y
343349
help
344350
Sets RRAMC's BOOTCONF region protection to disable writes.

tests/subsys/bootloader/b0_lock_rwx/Kconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ config TEST_B0_LOCK_REGION
1818
help
1919
Region 3 is used for NSIB protection. Other one for MCUBoot.
2020

21+
config TEST_B0_LOCK_USE_S1
22+
bool "MCUBoot is running from S1 image for the test"
23+
2124
menu "Zephyr"
2225
source "Kconfig.zephyr"
2326
endmenu
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
cmake_minimum_required(VERSION 3.20.0)
8+
9+
if(CONFIG_TEST_B0_LOCK_USE_S1)
10+
zephyr_library()
11+
zephyr_library_sources(src/run_from_s1.c)
12+
endif()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
config TEST_B0_LOCK_USE_S1
8+
bool "Ensures the S1 image is started by b0"
9+
help
10+
This option causes b0 to invalidate the S0 image during startup,
11+
which ensures that the main function starts the S1 image.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build:
2+
cmake: zephyr
3+
kconfig: zephyr/Kconfig
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/init.h>
8+
#include <zephyr/sys/printk.h>
9+
#include <fw_info.h>
10+
#include <bl_storage.h>
11+
12+
static int invalidate_s0(void)
13+
{
14+
uint32_t s0_addr = s0_address_read();
15+
const struct fw_info *s0_info = fw_info_find(s0_addr);
16+
17+
printk("Invalidating S0 in order to ensure the S1 image is started\n");
18+
fw_info_invalidate(s0_info);
19+
20+
return 0;
21+
}
22+
23+
SYS_INIT(invalidate_s0, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

0 commit comments

Comments
 (0)