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/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/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. 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() 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 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) { 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