Introduction
Trusted Firmware-M (TF-M) divides memory into secure and non-secure regions. This guide explains how to add a custom non-secure flash region for data import purposes. This is useful for testing or extending TF-M functionality on platforms like FRDM-RW612.
This guide is using el2go_import_blob on frdmrw612//ns as a base example. You may use any TFM based example. Imported as a freestanding application using VSCode.

The initial two steps add a macro in order to easily add a #ifdef statement in the code in case you'd like to toggle the region on/off. TFM does not use the traditional Zephyr kConfig, so it is necessary to follow the steps to ensure the macro is defined correctly. If you want to add the region without the #if statement, skip steps 1-2. Make sure to remove #ifdef TFM_CUSTOM_DATA_IMPORT_REGION #endif from steps 3-5.
1. Enable the Feature via Configuration
File: el2go_import_blob/Kconfig
config TFM_CUSTOM_DATA_IMPORT_REGION
bool "Enable custom flash area for testing"
help
Validate non-secure region in flash.
File: el2go_import_blob/prj.conf
CONFIG_TFM_CUSTOM_DATA_IMPORT_REGION=y
2. Update Build System to Pass the Flag
File: el2go_import_blob/CMakeLists.txt
Append the CMake option:
if(CONFIG_TFM_CUSTOM_DATA_IMPORT_REGION)
set_property(TARGET zephyr_property_target APPEND PROPERTY TFM_CMAKE_OPTIONS
-DUSE_TFM_CUSTOM_DATA_IMPORT_REGION=ON
)
endif()
File: platform/ext/target/nxp/frdmrw612/config.cmake
Add the flag:
set(USE_TFM_CUSTOM_DATA_IMPORT_REGION OFF CACHE BOOL "")
File: platform/ext/target/nxp/frdmrw612/CMakeLists.txt
Add compile definition:
if (USE_TFM_CUSTOM_DATA_IMPORT_REGION)
set(TFM_CUSTOM_DATA_IMPORT_REGION_COMPILE_DEFINITION "TFM_CUSTOM_DATA_IMPORT_REGION")
endif()
target_compile_definitions(psa_interface INTERFACE
${TFM_CUSTOM_DATA_IMPORT_REGION_COMPILE_DEFINITION}
)
3. Define the Flash Region
File: platform/ext/target/nxp/frdmrw612/partition/flash_layout.h
Define address and size:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
#define TFM_CUSTOM_NS_REGION_ADDR (0x08500000)
#define TFM_CUSTOM_NS_REGION_SIZE (0x00001000) // 4KB
#endif
File: platform/ext/target/nxp/frdmrw612/partition/region_defs.h
Map the region:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
#define CUSTOM_NS_DATA_REGION_START (TFM_CUSTOM_NS_REGION_ADDR)
#define CUSTOM_NS_DATA_REGION_SIZE (TFM_CUSTOM_NS_REGION_SIZE)
#endif
4. Update Linker Script
File: platform/ext/target/nxp/common/gcc/tfm_common_s.ld
Add region base declaration:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
Load$$LR$$LR_CUSTOM_DATA_IMPORT_REGION$$Base = CUSTOM_NS_DATA_REGION_START;
#endif
5. Declare and Initialize Region in Code
File: platform/ext/target/nxp/common/target_cfg_common.h
Add to memory region struct:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
uint32_t custom_ns_data_region_base;
uint32_t custom_ns_data_region_limit;
#endif
File: platform/ext/target/nxp/common/tfm_hal_platform.c
Initialize region limits:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
REGION_DECLARE(Load$$LR$$, LR_CUSTOM_DATA_IMPORT_REGION, $$Base);
#endif // TFM_CUSTOM_DATA_IMPORT_REGION
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
.custom_ns_data_region_base =
(uint32_t)®ION_NAME(Load$$LR$$, LR_CUSTOM_DATA_IMPORT_REGION, $$Base),
.custom_ns_data_region_limit =
(uint32_t)®ION_NAME(Load$$LR$$, LR_CUSTOM_DATA_IMPORT_REGION, $$Base) + CUSTOM_NS_DATA_REGION_SIZE - 1,
#endif // TFM_CUSTOM_DATA_IMPORT_REGION
File: platform/ext/target/nxp/common/tfm_hal_isolation.c
Configure SAU:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
SECURE_WRITE_REGISTER(&(SAU->RNR), 7U);
SAU->RBAR = (memory_regions.custom_ns_data_region_base & SAU_RBAR_BADDR_Msk);
SAU->RLAR = (memory_regions.custom_ns_data_region_limit & SAU_RLAR_LADDR_Msk) | SAU_RLAR_ENABLE_Msk;
#endif
File: platform/ext/target/nxp/frdmrw612/target_cfg.c
Configure MPC:
#ifdef TFM_CUSTOM_DATA_IMPORT_REGION
enable_mem_rule_for_partition(memory_regions.custom_ns_data_region_base, memory_regions.custom_ns_data_region_limit);
#endif
6. Update Device Tree Overlay
File: zephyr/samples/el2go_import_blob/frdm_rw612_rw612_ns.overlay
Add partition:
custom_ns_data: partition@500000 {
label = "custom_ns_data";
reg = <0x500000 0x1000>;
};
Let's test the region by erasing, writing and reading.
1. Enable the Zephyr flash
File: el2go_import_blob/prj.conf
CONFIG_FLASH=y
2. Add some test code to the main source file of the project.
#include <zephyr/drivers/flash.h>
const struct device *flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller));
static int test_flash_region(void) {
uint32_t offset = 0x500000;
uint32_t test_data = 0xDEADBEEF;
uint32_t read_data;
// 1. Erase flash (required before write)
flash_erase(flash_dev, offset, 4096);
// 2. Write data
flash_write(flash_dev, offset, &test_data, sizeof(test_data));
// 3. Read data (this can use memcpy function)
memcpy(&read_data, (void *)0x08500000, sizeof(read_data));
if (read_data == test_data) {
LOG("✓ Flash test passed with memcpy: 0x%08x\n", read_data);
} else {
LOG("✗ Flash test failed\n");
}
//Flash read operation will have fix to correctly calculate address next release
/*flash_read(flash_dev, offset, &read_data, sizeof(read_data));
if (read_data == test_data) {
LOG("✓ Flash test passed with flash_read: 0x%08x\n", read_data);
} else {
LOG("✗ Flash test failed\n");
}
*/
return 0;
}
3. Call the test_flash_region() function from the main().
