diff --git a/doc/guides/arch/arm_cortex_m.rst b/doc/guides/arch/arm_cortex_m.rst index 62bc96e06660a..1b1e87829aafa 100644 --- a/doc/guides/arch/arm_cortex_m.rst +++ b/doc/guides/arch/arm_cortex_m.rst @@ -440,12 +440,45 @@ are programmed during system boot. SRAM. (An exception to this setting is when :kconfig:option:`CONFIG_MPU_GAP_FILLING` is disabled (Arm v8-M only); in that case no SRAM MPU programming is done so the access is determined by the default Arm memory map policies, allowing for privileged-only RWX permissions on SRAM). +* All the memory regions defined in the devicetree with the compatible + :dtcompatible:`zephyr,memory-region` and at least the property + ``zephyr,memory-region-mpu`` defining the MPU permissions for the memory region. + See the next section for more details. The above MPU regions are defined in :file:`soc/arm/common/arm_mpu_regions.c`. Alternative MPU configurations are allowed by enabling :kconfig:option:`CONFIG_CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS`. When enabled, this option signifies that the Cortex-M SoC will define and configure its own fixed MPU regions in the SoC definition. +Fixed MPU regions defined in devicetree +--------------------------------------- + +The user can define memory regions to be allocated and created in the linker +script using nodes with the :dtcompatible:`zephyr,memory-region` devicetree +compatible. When the property ``zephyr,memory-region-mpu`` is present in such +a node, a new MPU region will be allocated and programmed during system +boot. + +The property ``zephyr,memory-region-mpu`` is a string carrying the attributes +for the MPU region. It is converted to a C token for use defining the attributes +of the MPU region. + +For example, to define a new non-cacheable memory region in devicetree: + +.. code-block:: devicetree + + sram_no_cache: memory@20300000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x20300000 0x100000>; + zephyr,memory-region = "SRAM_NO_CACHE"; + zephyr,memory-region-mpu = "RAM_NOCACHE"; + }; + +This will automatically create a new MPU entry in +:zephyr_file:`soc/arm/common/arm_mpu_regions.c` with the correct name, base, +size and attributes gathered directly from the devicetree. See +:zephyr_file:`include/linker/devicetree_regions.h` for more details. + Static MPU regions ------------------ diff --git a/dts/bindings/base/zephyr,memory-region.yaml b/dts/bindings/base/zephyr,memory-region.yaml index ad1b41478db64..f31f79cb0f474 100644 --- a/dts/bindings/base/zephyr,memory-region.yaml +++ b/dts/bindings/base/zephyr,memory-region.yaml @@ -16,3 +16,15 @@ properties: memory region in the final executable. The region address and size is taken from the property, while the name is the value of this property. + + zephyr,memory-region-mpu: + type: string + required: false + enum: + - "RAM" + - "RAM_NOCACHE" + - "FLASH" + description: | + Signify that this node should result in a dedicated MPU region. The + region address and size are taken from the property, while the MPU + attribute is the value of this property. diff --git a/include/linker/devicetree_regions.h b/include/linker/devicetree_regions.h index 638e9d199517c..2acbdb992600d 100644 --- a/include/linker/devicetree_regions.h +++ b/include/linker/devicetree_regions.h @@ -46,6 +46,8 @@ #define _DT_SECTION_SIZE(node_id) UTIL_CAT(_DT_SECTION_PREFIX(node_id), _size) #define _DT_SECTION_LOAD(node_id) UTIL_CAT(_DT_SECTION_PREFIX(node_id), _load_start) +#define _DT_ATTR(token) UTIL_CAT(UTIL_CAT(REGION_, token), _ATTR) + /** * @brief Declare a memory region * @@ -74,6 +76,35 @@ _DT_SECTION_SIZE(node_id) = _DT_SECTION_END(node_id) - _DT_SECTION_START(node_id); \ _DT_SECTION_LOAD(node_id) = LOADADDR(_DT_SECTION_NAME(node_id)); +/** + * Call the user-provided MPU_FN() macro passing the expected arguments + */ +#define _EXPAND_MPU_FN(node_id, MPU_FN, ...) \ + MPU_FN(LINKER_DT_NODE_REGION_NAME(node_id), \ + DT_REG_ADDR(node_id), \ + DT_REG_SIZE(node_id), \ + _DT_ATTR(DT_STRING_TOKEN(node_id, zephyr_memory_region_mpu))), + +/** + * Check that the node_id has both properties: + * - zephyr,memory-region-mpu + * - zephyr,memory-region + * + * and call the EXPAND_MPU_FN() macro + */ +#define _CHECK_ATTR_FN(node_id, EXPAND_MPU_FN, ...) \ + COND_CODE_1(UTIL_AND(DT_NODE_HAS_PROP(node_id, zephyr_memory_region_mpu), \ + DT_NODE_HAS_PROP(node_id, zephyr_memory_region)), \ + (EXPAND_MPU_FN(node_id, __VA_ARGS__)), \ + ()) + +/** + * Call _CHECK_ATTR_FN() for each enabled node passing EXPAND_MPU_FN() as + * explicit argument and the user-provided MPU_FN() macro in __VA_ARGS__ + */ +#define _CHECK_APPLY_FN(compat, EXPAND_MPU_FN, ...) \ + DT_FOREACH_STATUS_OKAY_VARGS(compat, _CHECK_ATTR_FN, EXPAND_MPU_FN, __VA_ARGS__) + /** @endcond */ /** @@ -93,3 +124,78 @@ */ #define LINKER_DT_SECTIONS() \ DT_FOREACH_STATUS_OKAY(_DT_COMPATIBLE, _SECTION_DECLARE) + +/** + * @brief Generate MPU regions from the device tree nodes with compatible + * 'zephyr,memory-region' and 'zephyr,memory-region-mpu' attribute. + * + * Helper macro to apply an MPU_FN macro to all the memory regions declared + * using the 'zephyr,memory-region-mpu' property and the 'zephyr,memory-region' + * compatible. + * + * @p MPU_FN must take the form: + * + * @code{.c} + * #define MPU_FN(name, base, size, attr) ... + * @endcode + * + * The 'name', 'base' and 'size' parameters are taken from the DT node. + * + * The 'zephyr,memory-region-mpu' enum property is passed as an extended token + * to the MPU_FN macro using the 'attr' parameter, in the form + * REGION_{attr}_ATTR. + * + * Currently only three enums are supported for the 'zephyr,memory-region-mpu' + * property: + * + * - RAM + * - RAM_NOCACHE + * - FLASH + * + * This means that usually the arch code would provide some macros or defines + * with the same name of the extended property, that is: + * + * - REGION_RAM_ATTR + * - REGION_RAM_NOCACHE_ATTR + * - REGION_FLASH_ATTR + * + * Example devicetree fragment: + * + * / { + * soc { + * sram1: memory@2000000 { + * zephyr,memory-region = "MY_NAME"; + * zephyr,memory-region-mpu = "RAM_NOCACHE"; + * }; + * }; + * }; + * + * The 'attr' parameter of the MPU_FN function will be the extended + * 'REGION_RAM_NOCACHE_ATTR' token and the arch code will be usually + * implementing a macro with the same name. + * + * Example: + * + * @code{.c} + * + * #define REGION_RAM_NOCACHE_ATTR 0xAAAA + * #define REGION_RAM_ATTR 0xBBBB + * #define REGION_FLASH_ATTR 0xCCCC + * + * #define MPU_FN(p_name, p_base, p_size, p_attr) \ + * { \ + * .name = p_name, \ + * .base = p_base, \ + * .size = p_size, \ + * .attr = p_attr, \ + * } + * + * static const struct arm_mpu_region mpu_regions[] = { + * ... + * LINKER_DT_REGION_MPU(MPU_FN) + * ... + * }; + * @endcode + * + */ +#define LINKER_DT_REGION_MPU(mpu_fn) _CHECK_APPLY_FN(_DT_COMPATIBLE, _EXPAND_MPU_FN, mpu_fn) diff --git a/soc/arm/common/cortex_m/arm_mpu_mem_cfg.h b/soc/arm/common/cortex_m/arm_mpu_mem_cfg.h index dbcaa55330268..34dbbe9cd8054 100644 --- a/soc/arm/common/cortex_m/arm_mpu_mem_cfg.h +++ b/soc/arm/common/cortex_m/arm_mpu_mem_cfg.h @@ -62,6 +62,48 @@ #error "Unsupported sram size configuration" #endif +#define MPU_REGION_SIZE_32 REGION_32B +#define MPU_REGION_SIZE_64 REGION_64B +#define MPU_REGION_SIZE_128 REGION_128B +#define MPU_REGION_SIZE_256 REGION_256B +#define MPU_REGION_SIZE_512 REGION_512B +#define MPU_REGION_SIZE_1024 REGION_1K +#define MPU_REGION_SIZE_2048 REGION_2K +#define MPU_REGION_SIZE_4096 REGION_4K +#define MPU_REGION_SIZE_8192 REGION_8K +#define MPU_REGION_SIZE_16384 REGION_16K +#define MPU_REGION_SIZE_32768 REGION_32K +#define MPU_REGION_SIZE_65536 REGION_64K +#define MPU_REGION_SIZE_131072 REGION_128K +#define MPU_REGION_SIZE_262144 REGION_256K +#define MPU_REGION_SIZE_524288 REGION_512K +#define MPU_REGION_SIZE_1048576 REGION_1M +#define MPU_REGION_SIZE_2097152 REGION_2M +#define MPU_REGION_SIZE_4194304 REGION_4M +#define MPU_REGION_SIZE_8388608 REGION_8M +#define MPU_REGION_SIZE_16777216 REGION_16M +#define MPU_REGION_SIZE_33554432 REGION_32M +#define MPU_REGION_SIZE_67108864 REGION_64M +#define MPU_REGION_SIZE_134217728 REGION_128M +#define MPU_REGION_SIZE_268435456 REGION_256M +#define MPU_REGION_SIZE_536870912 REGION_512M + +#define MPU_REGION_SIZE(x) MPU_REGION_SIZE_ ## x + +#define ARM_MPU_REGION_INIT(p_name, p_base, p_size, p_attr) \ + { .name = p_name, \ + .base = p_base, \ + .attr = p_attr(MPU_REGION_SIZE(p_size)), \ + } + +#else + +#define ARM_MPU_REGION_INIT(p_name, p_base, p_size, p_attr) \ + { .name = p_name, \ + .base = p_base, \ + .attr = p_attr(p_base, p_size), \ + } + #endif /* !ARMV8_M_BASELINE && !ARMV8_M_MAINLINE */ #endif /* _ARM_CORTEX_M_MPU_MEM_CFG_H_ */ diff --git a/soc/arm/common/cortex_m/arm_mpu_regions.c b/soc/arm/common/cortex_m/arm_mpu_regions.c index 6ba05377ba531..15e6fd7c5bac5 100644 --- a/soc/arm/common/cortex_m/arm_mpu_regions.c +++ b/soc/arm/common/cortex_m/arm_mpu_regions.c @@ -6,6 +6,7 @@ #include #include +#include #include "arm_mpu_mem_cfg.h" @@ -28,6 +29,9 @@ static const struct arm_mpu_region mpu_regions[] = { #else REGION_RAM_ATTR(REGION_SRAM_SIZE)), #endif + + /* DT-defined regions */ + LINKER_DT_REGION_MPU(ARM_MPU_REGION_INIT) }; const struct arm_mpu_config mpu_config = { diff --git a/tests/misc/arm_mpu_regions/CMakeLists.txt b/tests/misc/arm_mpu_regions/CMakeLists.txt new file mode 100644 index 0000000000000..b847ef0b65da9 --- /dev/null +++ b/tests/misc/arm_mpu_regions/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(arm_mpu_regions) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/misc/arm_mpu_regions/boards/mps2_an385.overlay b/tests/misc/arm_mpu_regions/boards/mps2_an385.overlay new file mode 100644 index 0000000000000..e882e13075d1b --- /dev/null +++ b/tests/misc/arm_mpu_regions/boards/mps2_an385.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Carlo Caione + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + /delete-node/ memory@20000000; + + sram0: memory@20000000 { + compatible = "mmio-sram"; + reg = <0x20000000 0x200000>; + }; + + sram_cache: memory@20200000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x20200000 0x100000>; + zephyr,memory-region = "SRAM_CACHE"; + zephyr,memory-region-mpu = "RAM"; + }; + + sram_no_cache: memory@20300000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x20300000 0x100000>; + zephyr,memory-region = "SRAM_NO_CACHE"; + zephyr,memory-region-mpu = "RAM_NOCACHE"; + }; + + sram_dtcm_fake: memory@abcdabcd { + compatible = "zephyr,memory-region", "arm,dtcm"; + reg = <0xabcdabcd 0x100000>; + zephyr,memory-region = "SRAM_DTCM_FAKE"; + zephyr,memory-region-mpu = "RAM"; + }; + + sram_no_mpu: memory@deaddead { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0xdeaddead 0x100000>; + zephyr,memory-region = "SRAM_NO_MPU"; + }; +}; diff --git a/tests/misc/arm_mpu_regions/prj.conf b/tests/misc/arm_mpu_regions/prj.conf new file mode 100644 index 0000000000000..9467c2926896d --- /dev/null +++ b/tests/misc/arm_mpu_regions/prj.conf @@ -0,0 +1 @@ +CONFIG_ZTEST=y diff --git a/tests/misc/arm_mpu_regions/src/main.c b/tests/misc/arm_mpu_regions/src/main.c new file mode 100644 index 0000000000000..322c7a86bfb36 --- /dev/null +++ b/tests/misc/arm_mpu_regions/src/main.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Carlo Caione + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +extern const struct arm_mpu_config mpu_config; + +static arm_mpu_region_attr_t cacheable = REGION_RAM_ATTR(REGION_1M); +static arm_mpu_region_attr_t noncacheable = REGION_RAM_NOCACHE_ATTR(REGION_1M); + +static void test_regions(void) +{ + int cnt = 0; + + for (size_t i = 0; i < mpu_config.num_regions; i++) { + const struct arm_mpu_region *r = &mpu_config.mpu_regions[i]; + + if (!strcmp(r->name, "SRAM_CACHE")) { + zassert_equal(r->base, 0x20200000, "Wrong base"); + zassert_equal(r->attr.rasr, cacheable.rasr, + "Wrong attr for SRAM_CACHE"); + cnt++; + } else if (!strcmp(r->name, "SRAM_NO_CACHE")) { + zassert_equal(r->base, 0x20300000, "Wrong base"); + zassert_equal(r->attr.rasr, noncacheable.rasr, + "Wrong attr for SRAM_NO_CACHE"); + cnt++; + } else if (!strcmp(r->name, "SRAM_DTCM_FAKE")) { + zassert_equal(r->base, 0xabcdabcd, "Wrong base"); + zassert_equal(r->attr.rasr, cacheable.rasr, + "Wrong attr for SRAM_DTCM_FAKE"); + cnt++; + } + } + + if (cnt != 3) { + /* + * SRAM0 and SRAM_NO_MPU should not create any MPU region. + * Check that. + */ + ztest_test_fail(); + } +} + +void test_main(void) +{ + ztest_test_suite(test_c_arm_mpu_regions, + ztest_unit_test(test_regions) + ); + + ztest_run_test_suite(test_c_arm_mpu_regions); +} diff --git a/tests/misc/arm_mpu_regions/testcase.yaml b/tests/misc/arm_mpu_regions/testcase.yaml new file mode 100644 index 0000000000000..500f426868696 --- /dev/null +++ b/tests/misc/arm_mpu_regions/testcase.yaml @@ -0,0 +1,4 @@ +tests: + misc.arm_mpu_regions: + platform_allow: mps2_an385 + tags: sample board sram mpu