From 1c43b9bf416462ffda4cf3da1066767574ee8fc2 Mon Sep 17 00:00:00 2001 From: Sumit Batra Date: Sun, 12 Oct 2025 01:27:47 +0530 Subject: [PATCH 1/5] drivers: flash: add MCUX C40 flash driver for NXP S32K3x Add a Zephyr flash shim for the on-chip C40 flash controller used on NXP S32K3x (e.g. S32K344). The driver is backed by the MCUX C40 HAL and implements read/erase/program, page layout, and an optional protection policy that can lock well-known regions (IVT/MCUboot) derived from devicetree. Key details: - Selects FLASH_HAS_DRIVER_ENABLED / FLASH_HAS_EXPLICIT_ERASE / FLASH_HAS_PAGE_LAYOUT. - Runs erase/program from SRAM when XIP by relocating both the shim and the MCUX HAL source if CODE_DATA_RELOCATION_SRAM=y. - Optional protection pass at init (FLASH_MCUX_C40_APPLY_PROTECTION), which aligns windows to sector boundaries and applies lock/unlock using the HAL. This is useful on XIP systems to keep IVT/bootloader ranges read-only; can be disabled if a bootloader or security policy manages protection instead. Files: - drivers/flash/flash_mcux_c40.c (new) - drivers/flash/CMakeLists.txt (+zephyr_code_relocate when needed) - drivers/flash/Kconfig.mcux (+FLASH_MCUX_C40_API, +FLASH_MCUX_C40_APPLY_PROTECTION) - modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake (+driver.flash_c40) Signed-off-by: Sumit Batra --- drivers/flash/CMakeLists.txt | 10 + drivers/flash/Kconfig.mcux | 29 ++ drivers/flash/flash_mcux_c40.c | 377 ++++++++++++++++++ .../mcux/mcux-sdk-ng/drivers/drivers.cmake | 2 + 4 files changed, 418 insertions(+) create mode 100644 drivers/flash/flash_mcux_c40.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 2ff0f66a59084..b285802aa2867 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -167,6 +167,16 @@ if(CONFIG_FLASH_NXP_S32_QSPI_NOR OR CONFIG_FLASH_NXP_S32_QSPI_HYPERFLASH) zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/memc) endif() +zephyr_library_sources_ifdef(CONFIG_FLASH_MCUX_C40_API flash_mcux_c40.c) +# Run the driver from RAM or another relocation source when relocation is enabled +if(CONFIG_FLASH_MCUX_C40_API AND CONFIG_CODE_DATA_RELOCATION) + zephyr_code_relocate(FILES ${CMAKE_CURRENT_LIST_DIR}/flash_mcux_c40.c LOCATION RAM) + if(DEFINED ZEPHYR_HAL_NXP_MODULE_DIR) + # Relocating HAL driver from Zephyr keeping HAL RTOS-agnostic + zephyr_code_relocate(FILES ${ZEPHYR_HAL_NXP_MODULE_DIR}/mcux/mcux-sdk-ng/drivers/flash_c40/fsl_c40_flash.c LOCATION RAM) + endif() +endif() + if(CONFIG_SOC_FLASH_RENESAS_RA_HP) zephyr_library_sources(soc_flash_renesas_ra_hp.c) zephyr_library_sources_ifdef(CONFIG_FLASH_EX_OP_ENABLED soc_flash_renesas_ra_hp_ex_op.c) diff --git a/drivers/flash/Kconfig.mcux b/drivers/flash/Kconfig.mcux index dc15e01a394c5..05f862c45b5a9 100644 --- a/drivers/flash/Kconfig.mcux +++ b/drivers/flash/Kconfig.mcux @@ -118,3 +118,32 @@ choice FLASH_LOG_LEVEL_CHOICE endchoice endif # DT_HAS_NXP_IMX_FLEXSPI_ENABLED + +# MCUX C40 internal flash API shim (S32K3x) +config FLASH_MCUX_C40_API + def_bool y + depends on SOC_SERIES_S32K3 + depends on DT_HAS_NXP_S32K3X_C40_FLASH_ENABLED + select FLASH_HAS_DRIVER_ENABLED + select FLASH_HAS_EXPLICIT_ERASE + select FLASH_HAS_PAGE_LAYOUT + # C40 uses controller commands, not CPU stores to flash + # so we are not relaxing MPU for ROM writes. + # Relocate driver/HAL when XIP so erase/program run from SRAM: + imply CODE_DATA_RELOCATION if XIP + imply CODE_DATA_RELOCATION_SRAM if XIP + help + Enable the MCUX C40 internal flash API shim used on NXP S32K3x + (e.g. S32K344). Provides Zephyr flash driver glue for &flash0 using + the MCUX C40 HAL. Needed for FLASH_MAP/MCUboot on internal flash + +# NXP MCUX C40 internal flash: optional protection pass at init +config FLASH_MCUX_C40_APPLY_PROTECTION + bool "Apply default protection windows (IVT/MCUboot) at init" + depends on FLASH_MCUX_C40_API + default y if XIP + help + When enabled, the driver locks/unlocks protection for well-known + regions derived from devicetree (e.g. ivt_header, ivt_pad, mcuboot) + during driver init. Useful on XIP systems to keep IVT/bootloader + areas read-only. Disable if your bootloader/policy manages this. diff --git a/drivers/flash/flash_mcux_c40.c b/drivers/flash/flash_mcux_c40.c new file mode 100644 index 0000000000000..be24c0d6b1aa9 --- /dev/null +++ b/drivers/flash/flash_mcux_c40.c @@ -0,0 +1,377 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + * + * Zephyr shim for NXP S32K3x C40 internal flash + */ + +#define DT_DRV_COMPAT nxp_s32k3x_c40_flash + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_ARM) +#include +#endif +LOG_MODULE_REGISTER(flash_mcux_c40, CONFIG_FLASH_LOG_LEVEL); + +#include "fsl_c40_flash.h" + +/* -------- Helpers -------- */ + +static inline int mcux_to_errno(status_t s) +{ + switch (s) { + case kStatus_FLASH_Success: + return 0; + case kStatus_FLASH_InvalidArgument: + case kStatus_FLASH_SizeError: + case kStatus_FLASH_AlignmentError: + case kStatus_FLASH_AddressError: + return -EINVAL; + case kStatus_FLASH_AccessError: + case kStatus_FLASH_ProtectionViolation: + case kStatus_FLASH_CommandFailure: + return -EIO; + case kStatus_FLASH_EraseKeyError: + return -EPERM; + default: + return -EIO; + } +} + +struct prot_range { + uint32_t off, len; + const char *name; +}; + +struct mcux_c40_cfg { + uint32_t base; /* flash XIP base */ + uint32_t size; /* total bytes covered by this instance */ + uint32_t erase_block; /* 8 KiB on C40 */ + uint32_t write_block; /* 8 bytes min program unit */ + const struct flash_parameters *params; +#if defined(CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION) + const struct prot_range *prot_tbl; + size_t prot_cnt; +#endif +}; + +struct mcux_c40_data { + flash_config_t cfg; /* MCUX HAL context */ +}; + +static inline bool intersects(uint32_t a_off, uint32_t a_len, uint32_t b_off, uint32_t b_len) +{ + const uint32_t a_end = a_off + a_len; + const uint32_t b_end = b_off + b_len; + return (a_off < b_end) && (b_off < a_end); +} + +/* -------- Flash API -------- */ + +static int mcux_c40_read(const struct device *dev, off_t off, void *buf, size_t len) +{ + const struct mcux_c40_cfg *cfg = dev->config; + + if ((off < 0) || ((size_t)off + len > cfg->size)) { + return -EINVAL; + } + + memcpy(buf, (const void *)(cfg->base + (uintptr_t)off), len); + return 0; +} + +static int mcux_c40_write(const struct device *dev, off_t off, const void *buf, size_t len) +{ + const struct mcux_c40_cfg *cfg = dev->config; + struct mcux_c40_data *data = dev->data; + + /* Bounds check */ + if ((off < 0) || ((size_t)off + len > cfg->size)) { + return -EINVAL; + } + + /* The HAL enforces alignment to C40 constraints: + * minimum write chunk is C40_WRITE_SIZE_MIN (8 bytes) + * writes are most efficient in quad-page (128 bytes) + * We perform a light check: write must be multiple of write_block, + * and start aligned to write_block. For more flexibility, you could + * implement a read-modify-write cache; for now we map 1:1 to HAL. + */ + if (((uintptr_t)off % cfg->write_block) != 0 || (len % cfg->write_block) != 0) { + return -EINVAL; + } + + unsigned int key = irq_lock(); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + __ISB(); + } + + status_t st = FLASH_Program(&data->cfg, (uint32_t)(cfg->base + (uintptr_t)off), + (uint32_t *)buf, (uint32_t)len); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + } + + irq_unlock(key); + + /* The array changed behind the CPU; drop stale D-cache lines covering the range */ + sys_cache_data_invd_range((void *)(cfg->base + (uintptr_t)off), len); + + return mcux_to_errno(st); +} + +static int mcux_c40_erase(const struct device *dev, off_t off, size_t len) +{ + const struct mcux_c40_cfg *cfg = dev->config; + struct mcux_c40_data *data = dev->data; + + if ((off < 0) || ((size_t)off + len > cfg->size)) { + return -EINVAL; + } + if (((uintptr_t)off % cfg->erase_block) != 0 || (len % cfg->erase_block) != 0) { + return -EINVAL; + } + + unsigned int key = irq_lock(); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + __ISB(); + } + + status_t st = FLASH_Erase(&data->cfg, (uint32_t)(cfg->base + (uintptr_t)off), (uint32_t)len, + kFLASH_ApiEraseKey); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + } + + irq_unlock(key); + + sys_cache_data_invd_range((void *)(cfg->base + (uintptr_t)off), len); + return mcux_to_errno(st); +} + +static const struct flash_parameters *mcux_c40_get_parameters(const struct device *dev) +{ + const struct mcux_c40_cfg *cfg = dev->config; + return cfg->params; +} + +#ifdef CONFIG_FLASH_PAGE_LAYOUT +static void mcux_c40_pages_layout(const struct device *dev, + const struct flash_pages_layout **layout, size_t *layout_size) +{ + const struct mcux_c40_cfg *cfg = dev->config; + static struct flash_pages_layout l; + + l.pages_count = cfg->size / cfg->erase_block; + l.pages_size = cfg->erase_block; + + *layout = &l; + *layout_size = 1; +} +#endif + +/* -------- Optional “lock policy” executed at init (opt-in via Kconfig) -------- */ +#if defined(CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION) +/* We place this TU into .ram_text_reloc via CMake, so no special attribute is needed. */ +static __attribute__((noinline)) status_t c40_apply_protection(flash_config_t *fcfg, + uint32_t flash_base, + uint32_t total_sz, uint32_t erase_sz, + const struct prot_range *pr, + size_t npr) +{ + for (uint32_t off = 0; off < total_sz; off += erase_sz) { + bool lock_it = false; + + for (size_t i = 0; i < npr; i++) { + if (intersects(off, erase_sz, pr[i].off, pr[i].len)) { + lock_it = true; + break; + } + } + const uint32_t abs = flash_base + off; + + status_t ps = FLASH_GetSectorProtection(fcfg, abs); + if (lock_it) { + if (ps != kStatus_FLASH_SectorLocked) { + unsigned int key = irq_lock(); + + if (IS_ENABLED(CONFIG_ARM)) { + /* Didn't use __ISB() here since + * there is no instruction side stream change */ + __DSB(); + } + + status_t s = FLASH_SetSectorProtection(fcfg, abs, true); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + } + + irq_unlock(key); + if (s != kStatus_FLASH_Success) { + return s; + } + } + } else { + if (ps != kStatus_FLASH_SectorUnlocked) { + unsigned int key = irq_lock(); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + } + + status_t s = FLASH_SetSectorProtection(fcfg, abs, false); + + if (IS_ENABLED(CONFIG_ARM)) { + __DSB(); + } + + irq_unlock(key); + if (s != kStatus_FLASH_Success) { + return s; + } + } + } + } + return kStatus_FLASH_Success; +} +#endif /* CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION */ + +static int mcux_c40_init(const struct device *dev) +{ + const struct mcux_c40_cfg *cfg = dev->config; + struct mcux_c40_data *data = dev->data; + + status_t st = FLASH_Init(&data->cfg); + if (st != kStatus_FLASH_Success) { + LOG_ERR("FLASH_Init failed: %d", (int)st); + return mcux_to_errno(st); + } + + LOG_INF("C40 flash: base=0x%lx size=0x%lx erase=0x%lx write=0x%lx", + (unsigned long)cfg->base, (unsigned long)cfg->size, (unsigned long)cfg->erase_block, + (unsigned long)cfg->write_block); + +#if defined(CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION) + /* Align protected windows to sector boundaries */ + struct prot_range prot_aligned[8]; + size_t nprot_al = 0; + + for (size_t i = 0; i < cfg->prot_cnt && nprot_al < ARRAY_SIZE(prot_aligned); i++) { + uint32_t s = cfg->prot_tbl[i].off; + uint32_t e = s + cfg->prot_tbl[i].len; + uint32_t sa = ROUND_DOWN(s, cfg->erase_block); + uint32_t ea = ROUND_UP(e, cfg->erase_block); + if (sa >= cfg->size) { + continue; + } + if (ea > cfg->size) { + ea = cfg->size; + } + + prot_aligned[nprot_al] = (struct prot_range){ + .off = sa, + .len = ea - sa, + .name = cfg->prot_tbl[i].name, + }; + nprot_al++; + } + + status_t s = c40_apply_protection(&data->cfg, cfg->base, cfg->size, cfg->erase_block, + prot_aligned, nprot_al); + if (s != kStatus_FLASH_Success) { + LOG_ERR("Protection apply failed: %d", (int)s); + return mcux_to_errno(s); + } + LOG_INF("Protection policy applied (%u window%s)", (unsigned)nprot_al, + (nprot_al == 1 ? "" : "s")); +#endif + return 0; +} + +/* -------- Driver API table -------- */ + +static const struct flash_driver_api mcux_c40_api = { + .read = mcux_c40_read, + .write = mcux_c40_write, + .erase = mcux_c40_erase, + .get_parameters = mcux_c40_get_parameters, +#ifdef CONFIG_FLASH_PAGE_LAYOUT + .page_layout = mcux_c40_pages_layout, +#endif +}; + +/* Per-instance params from DT */ + +#define C40_PARAMS(inst) \ + static const struct flash_parameters mcux_c40_params_##inst = { \ + .write_block_size = DT_PROP(DT_INST(inst, DT_DRV_COMPAT), write_block_size), \ + .erase_value = 0xFF, \ + } + +#if defined(CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION) + +/* Emit one initializer element if the label exists and belongs to this inst */ +#define C40_PROT_ENTRY(lbl, inst) \ + COND_CODE_1( \ + DT_NODE_HAS_STATUS(DT_NODELABEL(lbl), okay), \ + ( COND_CODE_1( \ + DT_SAME_NODE( \ + DT_PARENT(DT_PARENT(DT_NODELABEL(lbl))), \ + DT_INST(inst, DT_DRV_COMPAT) \ + ), \ + ( { .off = (uint32_t)FIXED_PARTITION_OFFSET(lbl), \ + .len = (uint32_t)FIXED_PARTITION_SIZE(lbl), \ + .name = #lbl }, ), \ + () ) \ + ), \ + () ) + +#define C40_MAKE_PROT_TBL(inst) \ + static const struct prot_range mcux_c40_prot_##inst[] = { \ + C40_PROT_ENTRY(ivt_header, inst) \ + C40_PROT_ENTRY(ivt_pad, inst) \ + C40_PROT_ENTRY(mcuboot, inst) /* common MCUboot label */ \ + C40_PROT_ENTRY(boot_partition, inst) /* fallback */ \ + }; \ + enum { mcux_c40_prot_cnt_##inst = ARRAY_SIZE(mcux_c40_prot_##inst) } + +#else +#define C40_MAKE_PROT_TBL(inst) +#endif + +#define C40_INIT(inst) \ + C40_MAKE_PROT_TBL(inst); \ + C40_PARAMS(inst); \ + static const struct mcux_c40_cfg mcux_c40_cfg_##inst = { \ + .base = DT_REG_ADDR(DT_INST(inst, DT_DRV_COMPAT)), \ + .size = DT_REG_SIZE(DT_INST(inst, DT_DRV_COMPAT)), \ + .erase_block = DT_PROP(DT_INST(inst, DT_DRV_COMPAT), erase_block_size), \ + .write_block = DT_PROP(DT_INST(inst, DT_DRV_COMPAT), write_block_size), \ + .params = &mcux_c40_params_##inst, \ + IF_ENABLED(CONFIG_FLASH_MCUX_C40_APPLY_PROTECTION, ( \ + .prot_tbl = mcux_c40_prot_##inst, \ + .prot_cnt = mcux_c40_prot_cnt_##inst, \ + )) \ + }; \ + static struct mcux_c40_data mcux_c40_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, mcux_c40_init, NULL, \ + &mcux_c40_data_##inst, &mcux_c40_cfg_##inst, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &mcux_c40_api); + +DT_INST_FOREACH_STATUS_OKAY(C40_INIT) diff --git a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake index e1ecc7c2bcb4a..900b21f98e0a4 100644 --- a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake +++ b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake @@ -229,6 +229,8 @@ if(CONFIG_SOC_MCXW716C OR CONFIG_SOC_MCXW727C OR CONFIG_SOC_MCXN947 OR CONFIG_SO set_variable_ifdef(CONFIG_SOC_FLASH_MCUX CONFIG_MCUX_COMPONENT_driver.flash_k4) endif() +set_variable_ifdef(CONFIG_FLASH_MCUX_C40_API CONFIG_MCUX_COMPONENT_driver.flash_c40) + if(CONFIG_SOC_SERIES_LPC51U68 OR CONFIG_SOC_SERIES_LPC54XXX) set_variable_ifdef(CONFIG_SOC_FLASH_MCUX CONFIG_MCUX_COMPONENT_driver.iap) endif() From ce921500e7fe68082eb7f2eeb3da15d9af2d5496 Mon Sep 17 00:00:00 2001 From: Sumit Batra Date: Sun, 12 Oct 2025 01:29:40 +0530 Subject: [PATCH 2/5] dts: nxp: s32k3: add C40 flash binding and SoC flash0 node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a DT binding for the S32K3x on-chip C40 flash and describe the SoC-level flash0 node in nxp_s32k344_m7.dtsi using the new compatible. - Binding: dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml (inherits soc-nv-flash.yaml; documents erase/write block sizes and NXP block geometry properties). - SoC node: flash0@0x00400000 with the new compatible and geometry properties. Keep status = "disabled" at the SoC level so boards opt-in. This prepares the platform for using Zephyr’s flash API / FLASH_MAP / MCUboot with internal code flash. Files: - dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml (new) - dts/arm/nxp/nxp_s32k344_m7.dtsi (define flash0, status = "disabled") Signed-off-by: Sumit Batra --- dts/arm/nxp/nxp_s32k344_m7.dtsi | 17 ++++++-------- .../nxp,s32k3x-c40-flash.yaml | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml diff --git a/dts/arm/nxp/nxp_s32k344_m7.dtsi b/dts/arm/nxp/nxp_s32k344_m7.dtsi index 21553748ba66f..f2678875d34a6 100644 --- a/dts/arm/nxp/nxp_s32k344_m7.dtsi +++ b/dts/arm/nxp/nxp_s32k344_m7.dtsi @@ -62,16 +62,13 @@ reg = <0x20400000 DT_SIZE_K(320)>; }; - /* - * Last 48Kb is reserved by Secure BAF, application core cannot access it. - * - * Do not assign the compatible for this now, when Flash API is implemented, - * need to check if "soc-nv-flash" can be used or a new binding need to be - * created, based on it. - */ - flash0: flash@400000 { - reg = <0x00400000 DT_SIZE_K(4048)>; - status = "disabled"; + /* Last 48Kb is reserved by Secure BAF, application core cannot access it. */ + flash0: flash@400000 { + compatible = "nxp,s32k3x-c40-flash"; + reg = <0x00400000 DT_SIZE_K(4048)>; + erase-block-size = <0x2000>; /* Min erase size is 8 KiB */ + write-block-size = <8>; /* Minimum programmable unit */ + status = "disabled"; }; clock: clock-controller@402c8000 { diff --git a/dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml b/dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml new file mode 100644 index 0000000000000..220b170907898 --- /dev/null +++ b/dts/bindings/flash_controller/nxp,s32k3x-c40-flash.yaml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +title: NXP S32K3x C40 Internal Flash Controller + +description: > + On-chip C40 flash used in NXP S32K3x MCUs (e.g. S32K344). This node + represents the internal code flash region (typically bound to &flash0) + used by Zephyr’s flash API / FLASH_MAP / MCUboot. + +compatible: "nxp,s32k3x-c40-flash" + +# Inherit common SoC flash schema (reg/status, partitions, etc.) +include: soc-nv-flash.yaml + +properties: + erase-block-size: + type: int + const: 8192 + description: Minimum erase size for C40 is 8 KiB. + + write-block-size: + type: int + const: 8 + description: Smallest programmable unit is 8 bytes. From d01c6aba4c22a0b3227abb534cf238c0a9cf31ab Mon Sep 17 00:00:00 2001 From: Sumit Batra Date: Sun, 12 Oct 2025 01:32:01 +0530 Subject: [PATCH 3/5] boards: nxp: mr_canhubk3: enable flash0 and add MCUboot overlays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable the SoC C40 internal flash on mr_canhubk3 and add a complete set of overlays to build MCUboot + an MCUboot-chained app using internal flash. - Enable &flash0 at the board level. - Add MCUboot partition layout overlay with ivt-header/ivt_pad/mcuboot/ image-0/image-1/image-scratch. - Add direction overlays: * mr_canhubk3_mcuboot_boot.overlay (bootloader links to &mcuboot) * mr_canhubk3_mcuboot_app.overlay (app links to &slot0_partition) - Add an option to enable booting through MCUboot, in application's overlay-mcuboot.conf - Add CONFIG_ROM_START_OFFSET=0x400 in board's Kconfig.defconfig To make bootloader and the sign tool compatible with S32K3 placing the vector table at +0x400 - Provide overlay-mcuboot-1kheader.conf, which passes CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--header-size 0x400" so the signed image uses a 1 KiB header, matching the S32K3 vector table alignment. - Update board docs (index.rst): step-by-step MCUboot flow, two signing options (0x200 vs 0x400 header), and example west build/sign/flash commands. This keeps base DTS neutral and puts MCUboot-specific enablement in overlays/config only. Example (MCUboot): west build -p always -b mr_canhubk3 bootloader/mcuboot/boot/zephyr -- \ -DEXTRA_DTC_OVERLAY_FILE="boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay;boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay" Example (App): west build -p always -b mr_canhubk3 -d build/flash_shell zephyr/samples/drivers/flash_shell -DEXTRA_DTC_OVERLAY_FILE="$PWD/zephyr/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay;$PWD/zephyr/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay" -DOVERLAY_CONFIG=overlay-mcuboot.conf -DEXTRA_CONF_FILE="$PWD/zephyr/boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf" west sign -d build/flash_shell -t imgtool --  --key "$PWD/bootloader/mcuboot/root-rsa-2048.pem" --header-size 0x400 Files: - boards/nxp/mr_canhubk3/mr_canhubk3.dts - boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay (new) - boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay (new) - boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay (new) - boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf (new) - boards/nxp/mr_canhubk3/doc/index.rst - boards/nxp/mr_canhubk3/Kconfig.defconfig - samples/drivers/flash_shell/overlay-mcuboot.conf (new) Signed-off-by: Sumit Batra --- boards/nxp/mr_canhubk3/Kconfig.defconfig | 10 +++ boards/nxp/mr_canhubk3/doc/index.rst | 75 +++++++++++++++++++ boards/nxp/mr_canhubk3/mr_canhubk3.dts | 2 + .../mr_canhubk3_mcuboot_app.overlay | 8 ++ .../mr_canhubk3_mcuboot_boot.overlay | 8 ++ .../mr_canhubk3_mcuboot_layout.overlay | 44 +++++++++++ .../mr_canhubk3/overlay-mcuboot-1kheader.conf | 1 + .../drivers/flash_shell/overlay-mcuboot.conf | 1 + 8 files changed, 149 insertions(+) create mode 100644 boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay create mode 100644 boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay create mode 100644 boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay create mode 100644 boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf create mode 100644 samples/drivers/flash_shell/overlay-mcuboot.conf diff --git a/boards/nxp/mr_canhubk3/Kconfig.defconfig b/boards/nxp/mr_canhubk3/Kconfig.defconfig index 9d280f7cf3f37..162172bdb9f04 100644 --- a/boards/nxp/mr_canhubk3/Kconfig.defconfig +++ b/boards/nxp/mr_canhubk3/Kconfig.defconfig @@ -33,4 +33,14 @@ config MDIO endif # NETWORKING +if BOOTLOADER_MCUBOOT + +# S32K3 places the vector table at +0x400 +# This makes the bootloader and the sign tool compatible with that. +config ROM_START_OFFSET + hex + default 0x400 + +endif + endif # BOARD_MR_CANHUBK3 diff --git a/boards/nxp/mr_canhubk3/doc/index.rst b/boards/nxp/mr_canhubk3/doc/index.rst index 94966ff84d832..8d7fe56bf471e 100644 --- a/boards/nxp/mr_canhubk3/doc/index.rst +++ b/boards/nxp/mr_canhubk3/doc/index.rst @@ -296,6 +296,81 @@ For example, to erase and verify flash content: west flash -r trace32 --startup-args elfFile=build/zephyr/zephyr.elf loadTo=flash eraseFlash=yes verifyFlash=yes +MCUboot chain-loading +===================== + +This board supports booting Zephyr applications via :ref:`mcuboot`. + +.. important:: + + On S32K3 the vector table is larger than 512 bytes, so MCUboot images + **must be signed with a 1 KiB (0x400) header**. You do **not** need to + change :kconfig:option:`CONFIG_ROM_START_OFFSET` in your app (the + S32K3 linker naturally aligns the vector table to 0x400 when needed), + but you **do** need to sign the image with ``--header-size 0x400``. + +Build and flash MCUboot +----------------------- + +Use the provided overlays to place MCUboot and the application slots in +internal flash (PFLASH): + +.. code-block:: console + + west build -b mr_canhubk3 -d build/mcuboot bootloader/mcuboot/boot/zephyr \ + -DEXTRA_DTC_OVERLAY_FILE="boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay;boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay" + + west flash -d build/mcuboot -r jlink + +Build the application for MCUboot +--------------------------------- + +Use the application overlay so the app links to the primary slot: + +.. code-block:: console + + west build -b mr_canhubk3 -d build/flash_shell samples/drivers/flash_shell \ + -DEXTRA_DTC_OVERLAY_FILE="boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay;boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay" \ + -DOVERLAY_CONFIG=samples/drivers/flash_shell/overlay-mcuboot.conf + +Sign the image with a 1 KiB header +---------------------------------- + +Either pass the extra header size when signing: + +.. code-block:: console + + west sign -d build/flash_shell -t imgtool -- \ + --key bootloader/mcuboot/root-rsa-2048.pem \ + --header-size 0x400 + +or bake it into the build with: + +.. code-block:: console + + -DMCUBOOT_EXTRA_IMGTOOL_ARGS="--header-size 0x400" + +Flash the signed application +---------------------------- + +.. code-block:: console + + west flash -d build/flash_shell -r jlink -f build/flash_shell/zephyr/zephyr.signed.hex + +After reset you should see MCUboot logs followed by the application banner +(e.g. the Flash Shell prompt). + +Troubleshooting +--------------- + +* If MCUboot prints *“Image in the primary slot is not valid”* or gets stuck + after *“Jumping to the first image slot”*, the image was likely signed with + a 512-byte header. Re-sign with ``--header-size 0x400`` and re-flash. + +* Do **not** add an IVT to MCUboot-chainloaded applications. The SoC support + emits the IVT for standalone/XIP images or for MCUboot itself; it is + automatically omitted for apps built with :kconfig:option:`CONFIG_BOOTLOADER_MCUBOOT`. + Debugging ========= diff --git a/boards/nxp/mr_canhubk3/mr_canhubk3.dts b/boards/nxp/mr_canhubk3/mr_canhubk3.dts index 19ca4fb0aa9ae..a18c39d9f9d71 100644 --- a/boards/nxp/mr_canhubk3/mr_canhubk3.dts +++ b/boards/nxp/mr_canhubk3/mr_canhubk3.dts @@ -216,6 +216,8 @@ }; &flash0 { + status = "disabled"; + partitions { compatible = "fixed-partitions"; #address-cells = <1>; diff --git a/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay new file mode 100644 index 0000000000000..7393eb583bb6d --- /dev/null +++ b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_app.overlay @@ -0,0 +1,8 @@ +/ { + chosen { + /* App links to primary slot when booted by MCUboot */ + zephyr,code-partition = &slot0_partition; + /* Ensure we are using internal flash as the main flash */ + zephyr,flash = &flash0; + }; +}; diff --git a/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay new file mode 100644 index 0000000000000..9b977a6aea024 --- /dev/null +++ b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_boot.overlay @@ -0,0 +1,8 @@ +/ { + chosen { + /* Bootloader image is linked to its own partition */ + zephyr,code-partition = &mcuboot; + /* Ensure we are using internal flash as the main flash */ + zephyr,flash = &flash0; + }; +}; diff --git a/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay new file mode 100644 index 0000000000000..c4153e4f0ff33 --- /dev/null +++ b/boards/nxp/mr_canhubk3/mr_canhubk3_mcuboot_layout.overlay @@ -0,0 +1,44 @@ +&flash0 { + /* Replace any board-default partitions with the MCUboot layout */ + /delete-node/ partitions; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + ivt_header: partition@0 { + label = "ivt-header"; + reg = <0x00000000 0x00000100>; + }; + + /* pad up to the 8 KiB sector boundary at 0x2000 */ + ivt_pad: partition@100 { + label = "ivt_pad"; + reg = <0x00000100 0x00001F00>; + }; + + /* MCUboot @ 0x2000 (128 KiB here; adjust if you use 64 KiB) */ + mcuboot: partition@2000 { + label = "mcuboot"; + reg = <0x00002000 0x00020000>; + }; + + /* Primary image (slot 0) */ + slot0_partition: partition@22000 { + label = "image-0"; + reg = <0x00022000 0x001C0000>; + }; + + /* Secondary image (slot 1) — start right after slot0 ends */ + slot1_partition: partition@1E2000 { + label = "image-1"; + reg = <0x001E2000 0x001C0000>; + }; + + /* Scratch (swap) area */ + scratch_partition: partition@3A2000 { + label = "image-scratch"; + reg = <0x003A2000 0x00040000>; + }; + }; +}; diff --git a/boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf b/boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf new file mode 100644 index 0000000000000..dc5ca25363755 --- /dev/null +++ b/boards/nxp/mr_canhubk3/overlay-mcuboot-1kheader.conf @@ -0,0 +1 @@ +CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--header-size 0x400" diff --git a/samples/drivers/flash_shell/overlay-mcuboot.conf b/samples/drivers/flash_shell/overlay-mcuboot.conf new file mode 100644 index 0000000000000..0608e7a866c00 --- /dev/null +++ b/samples/drivers/flash_shell/overlay-mcuboot.conf @@ -0,0 +1 @@ +CONFIG_BOOTLOADER_MCUBOOT=y From 38081d9d52465f6ee26402854b2b5080abb81688 Mon Sep 17 00:00:00 2001 From: Sumit Batra Date: Sun, 12 Oct 2025 01:32:59 +0530 Subject: [PATCH 4/5] soc: nxp: s32k3: gate IVT emission when chain-loaded by MCUboot Emit the IVT section and IVT header only when XIP and the image is either a standalone XIP app or MCUboot itself. Do not emit the IVT when the Zephyr image is chain-loaded by MCUboot (BOOTLOADER_MCUBOOT=y). - linker.ld/sections.ld: place .ivt_header at IVT_HEADER only under XIP && (!BOOTLOADER_MCUBOOT || MCUBOOT). Provide __ivt_region_start/end symbols. - soc.c: guard IVT struct under the same condition and mark it 'used' so the linker keeps it when needed. This avoids populating 0x400000 IVT from the app image while retaining it for MCUboot or standalone XIP use-cases. Files: - soc/nxp/s32/s32k3/linker.ld - soc/nxp/s32/s32k3/sections.ld - soc/nxp/s32/s32k3/soc.c Signed-off-by: Sumit Batra --- soc/nxp/s32/s32k3/linker.ld | 12 ++++++++++-- soc/nxp/s32/s32k3/sections.ld | 10 +++++++--- soc/nxp/s32/s32k3/soc.c | 12 +++++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/soc/nxp/s32/s32k3/linker.ld b/soc/nxp/s32/s32k3/linker.ld index 8681f1cd3f380..e821362e43347 100644 --- a/soc/nxp/s32/s32k3/linker.ld +++ b/soc/nxp/s32/s32k3/linker.ld @@ -4,12 +4,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifdef CONFIG_XIP +/* Emit IVT section only when XIP and (standalone app OR MCUboot itself) */ +#if defined(CONFIG_XIP) && (!defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_MCUBOOT)) + MEMORY { - IVT_HEADER (r) : ORIGIN = CONFIG_FLASH_BASE_ADDRESS + CONFIG_IVT_HEADER_OFFSET, + IVT_HEADER (r) : ORIGIN = (CONFIG_FLASH_BASE_ADDRESS + CONFIG_IVT_HEADER_OFFSET), LENGTH = CONFIG_IVT_HEADER_SIZE } + +/* Symbols for IVT */ +PROVIDE(__ivt_region_start = ORIGIN(IVT_HEADER)); +PROVIDE(__ivt_region_end = ORIGIN(IVT_HEADER) + LENGTH(IVT_HEADER)); + #endif +/* Include standard Zephyr ARM script */ #include diff --git a/soc/nxp/s32/s32k3/sections.ld b/soc/nxp/s32/s32k3/sections.ld index ed9634431a522..ac413c1cded7d 100644 --- a/soc/nxp/s32/s32k3/sections.ld +++ b/soc/nxp/s32/s32k3/sections.ld @@ -4,10 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifdef CONFIG_XIP +/* Emit IVT section only when XIP and (standalone app OR MCUboot itself) */ +#if defined(CONFIG_XIP) && (!defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_MCUBOOT)) -.ivt_header : { - KEEP(*(.ivt_header)); +/* --- IVT at flash base --- */ +.ivt_header ORIGIN(IVT_HEADER) : ALIGN(4) +{ + /* Using a wildcard makes it more robust */ + KEEP(*(.ivt_header*)); } > IVT_HEADER #else diff --git a/soc/nxp/s32/s32k3/soc.c b/soc/nxp/s32/s32k3/soc.c index e2ecba3c6fbfd..eee3efae4fa30 100644 --- a/soc/nxp/s32/s32k3/soc.c +++ b/soc/nxp/s32/s32k3/soc.c @@ -12,7 +12,10 @@ #include #include -#ifdef CONFIG_XIP +#if defined(CONFIG_XIP) +/* Emit IVT only for standalone XIP or MCUboot itself + * But not for the cases where Zephyr Image is loaded by MCUboot */ +#if !defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_MCUBOOT) /* Image Vector Table structure definition for S32K3XX */ struct ivt { uint32_t header; @@ -33,13 +36,15 @@ struct ivt { extern char _vector_start[]; /* + * Attribute 'used' forces the compiler to emit ivt_header + * even if nothing references it. * IVT for SoC S32K344, the minimal boot configuration is: * - Watchdog (SWT0) is disabled (default value). * - Non-Secure Boot is used (default value). * - Ungate clock for Cortex-M7_0 after boot. * - Application start address of Cortex-M7_0 is application's vector table. */ -const struct ivt ivt_header __attribute__((section(".ivt_header"))) = { +const struct ivt ivt_header __attribute__((section(".ivt_header"), used)) = { .header = IVT_MAGIC_MARKER, .boot_configure = 1, .cm7_0_start_address = (const void *)_vector_start, @@ -47,7 +52,8 @@ const struct ivt ivt_header __attribute__((section(".ivt_header"))) = { .cm7_2_start_address = NULL, .lc_configure = NULL, }; -#endif /* CONFIG_XIP */ +#endif +#endif void soc_early_init_hook(void) { From eea1be762b6fcbb3a83050d9dfc68c4da6d70934 Mon Sep 17 00:00:00 2001 From: Sumit Batra Date: Sun, 12 Oct 2025 01:37:09 +0530 Subject: [PATCH 5/5] west.yml: hal_nxp: update revision to pull in C40 flash driver Signed-off-by: Sumit Batra --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index a5154435a3cdf..ac06fe1f1f9a5 100644 --- a/west.yml +++ b/west.yml @@ -210,7 +210,7 @@ manifest: groups: - hal - name: hal_nxp - revision: 4377ecfba52fe0ff7352eadf426b523ed3e1d27f + revision: pull/618/head path: modules/hal/nxp groups: - hal