diff --git a/boards/arm/nrf52832_mdk/nrf52832_mdk.dts b/boards/arm/nrf52832_mdk/nrf52832_mdk.dts index 8a36945d1642e..c6d2a8640db69 100644 --- a/boards/arm/nrf52832_mdk/nrf52832_mdk.dts +++ b/boards/arm/nrf52832_mdk/nrf52832_mdk.dts @@ -150,6 +150,7 @@ storage_partition: partition@7a000 { label = "storage"; reg = <0x0007a000 0x00006000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52833dk_nrf52820/nrf52833dk_nrf52820.dts b/boards/arm/nrf52833dk_nrf52820/nrf52833dk_nrf52820.dts index 068ffe9089551..51c2996e19df6 100644 --- a/boards/arm/nrf52833dk_nrf52820/nrf52833dk_nrf52820.dts +++ b/boards/arm/nrf52833dk_nrf52820/nrf52833dk_nrf52820.dts @@ -148,6 +148,7 @@ storage_partition: partition@3a000 { label = "storage"; reg = <0x0003a000 0x00006000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts b/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts index 9b9afbaf85fb9..cba508824518e 100644 --- a/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts +++ b/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts @@ -217,6 +217,7 @@ arduino_spi: &spi3 { storage_partition: partition@7a000 { label = "storage"; reg = <0x0007A000 0x00006000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52840_blip/nrf52840_blip.dts b/boards/arm/nrf52840_blip/nrf52840_blip.dts index add674e56b266..b236327f922a5 100644 --- a/boards/arm/nrf52840_blip/nrf52840_blip.dts +++ b/boards/arm/nrf52840_blip/nrf52840_blip.dts @@ -166,6 +166,7 @@ storage_partition: partition@f8000 { label = "storage"; reg = <0x000f8000 0x00008000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52840_mdk/nrf52840_mdk.dts b/boards/arm/nrf52840_mdk/nrf52840_mdk.dts index 2f7438a0af060..079bdecdeb1fd 100644 --- a/boards/arm/nrf52840_mdk/nrf52840_mdk.dts +++ b/boards/arm/nrf52840_mdk/nrf52840_mdk.dts @@ -192,6 +192,7 @@ storage_partition: partition@f8000 { label = "storage"; reg = <0x000f8000 0x00008000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52840_papyr/nrf52840_papyr.dts b/boards/arm/nrf52840_papyr/nrf52840_papyr.dts index 901c1a18a1038..9a86dc301da19 100644 --- a/boards/arm/nrf52840_papyr/nrf52840_papyr.dts +++ b/boards/arm/nrf52840_papyr/nrf52840_papyr.dts @@ -164,6 +164,7 @@ storage_partition: partition@f8000 { label = "storage"; reg = <0x000f8000 0x00008000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52840dk_nrf52811/nrf52840dk_nrf52811.dts b/boards/arm/nrf52840dk_nrf52811/nrf52840dk_nrf52811.dts index 1b4a3c6a48383..b4d684bb83510 100644 --- a/boards/arm/nrf52840dk_nrf52811/nrf52840dk_nrf52811.dts +++ b/boards/arm/nrf52840dk_nrf52811/nrf52840dk_nrf52811.dts @@ -161,6 +161,7 @@ storage_partition: partition@29000 { label = "storage"; reg = <0x00029000 0x00007000>; + linker-region; }; }; }; diff --git a/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts b/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts index b76b648c2ec51..17ff42fdacb9a 100644 --- a/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts +++ b/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts @@ -274,6 +274,7 @@ arduino_spi: &spi3 { storage_partition: partition@f8000 { label = "storage"; reg = <0x000f8000 0x00008000>; + linker-region; }; }; }; diff --git a/dts/bindings/mtd/partition.yaml b/dts/bindings/mtd/partition.yaml index 4ad4642f86fd6..2087b6d68b5b4 100644 --- a/dts/bindings/mtd/partition.yaml +++ b/dts/bindings/mtd/partition.yaml @@ -24,6 +24,10 @@ child-binding: type: boolean required: false description: if the partition is read-only or not + linker-region: + type: boolean + required: false + description: generate a MEMORY region for this partition in the linker reg: type: array description: register space diff --git a/include/arch/arm/aarch32/cortex_a_r/scripts/linker.ld b/include/arch/arm/aarch32/cortex_a_r/scripts/linker.ld index def88f5a99ba7..d29735af69858 100644 --- a/include/arch/arm/aarch32/cortex_a_r/scripts/linker.ld +++ b/include/arch/arm/aarch32/cortex_a_r/scripts/linker.ld @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -42,7 +43,8 @@ #if CONFIG_FLASH_LOAD_SIZE > 0 #define ROM_SIZE CONFIG_FLASH_LOAD_SIZE #else - #define ROM_SIZE (CONFIG_FLASH_SIZE*1K - CONFIG_FLASH_LOAD_OFFSET) + #define ROM_USABLE_END DT_PARTITION_REGIONS_START(CONFIG_FLASH_SIZE*1K) + #define ROM_SIZE (ROM_USABLE_END - CONFIG_FLASH_LOAD_OFFSET) #endif #if defined(CONFIG_XIP) @@ -84,6 +86,8 @@ MEMORY SRAM (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE /* Used by and documented in include/linker/intlist.ld */ IDT_LIST (wx) : ORIGIN = (RAM_ADDR + RAM_SIZE), LENGTH = 2K + /* Add custom memory regions requested from devicetree */ + DT_PARTITION_REGIONS() } ENTRY(CONFIG_KERNEL_ENTRY) diff --git a/include/arch/arm/aarch32/cortex_m/scripts/linker.ld b/include/arch/arm/aarch32/cortex_m/scripts/linker.ld index faf53451accd5..b830560dac771 100644 --- a/include/arch/arm/aarch32/cortex_m/scripts/linker.ld +++ b/include/arch/arm/aarch32/cortex_m/scripts/linker.ld @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -48,7 +49,8 @@ #if CONFIG_FLASH_LOAD_SIZE > 0 #define ROM_SIZE CONFIG_FLASH_LOAD_SIZE #else -#define ROM_SIZE (CONFIG_FLASH_SIZE*1K - CONFIG_FLASH_LOAD_OFFSET) +#define ROM_USABLE_END DT_PARTITION_REGIONS_START(CONFIG_FLASH_SIZE*1K) +#define ROM_SIZE (ROM_USABLE_END - CONFIG_FLASH_LOAD_OFFSET) #endif #endif @@ -116,6 +118,8 @@ MEMORY #endif /* Used by and documented in include/linker/intlist.ld */ IDT_LIST (wx) : ORIGIN = (RAM_ADDR + RAM_SIZE), LENGTH = 2K + /* Add custom memory regions requested from devicetree */ + DT_PARTITION_REGIONS() } ENTRY(CONFIG_KERNEL_ENTRY) diff --git a/include/linker/devicetree_regions.h b/include/linker/devicetree_regions.h new file mode 100644 index 0000000000000..66c85e1304fbf --- /dev/null +++ b/include/linker/devicetree_regions.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Generate memory regions from devicetree partitions. + * Partitions are only considered if they exist under chosen/zephyr,flash. + */ + +/* Partition under chosen/zephyr,flash */ +#define _CHOSEN_PARTITIONS UTIL_CAT(DT_CHOSEN(zephyr_flash), _S_partitions) + +/* Declare a memory region definition if `linker-region` is set */ +#define _REGION_DECLARE(part) COND_CODE_1(DT_PROP(part, linker_region), \ + (DT_LABEL(part) (rx) : \ + ORIGIN = DT_REG_ADDR(part), \ + LENGTH = DT_REG_SIZE(part)), \ + ()) + +/* Return a memory region origin if `linker-region` is set*/ +#define _REGION_ADDR(part) COND_CODE_1(DT_PROP(part, linker_region), \ + (DT_REG_ADDR(part),), \ + ()) + +/* Apply a macro to chosen partition, if it exists */ +#define _REGION_APPLY(f) \ + COND_CODE_1(IS_ENABLED(UTIL_CAT(_CHOSEN_PARTITIONS, _EXISTS)), \ + (DT_FOREACH_CHILD(_CHOSEN_PARTITIONS, f)), ()) + +/** + * @brief Find the lowest start address of all devicetree linker regions + * + * The MIN symbol passed to `FOR_EACH_NESTED` does not resolve to the standard + * ternery operation macro. Instead it is pasted as a symbol into the final + * linker file, where MIN is a builtin function in ld. + * https://sourceware.org/binutils/docs/ld/Builtin-Functions.html + * + * @param def default return value if no custom partitions are present + */ +#define DT_PARTITION_REGIONS_START(def) \ + FOR_EACH_NESTED(MIN, def, _REGION_APPLY(_REGION_ADDR)) + +/** + * @brief Generate region definitions for all devicetree linker regions + */ +#define DT_PARTITION_REGIONS() _REGION_APPLY(_REGION_DECLARE) diff --git a/include/sys/util_internal.h b/include/sys/util_internal.h index c272fd0b25f8b..3e0814cd06205 100644 --- a/include/sys/util_internal.h +++ b/include/sys/util_internal.h @@ -704,6 +704,43 @@ #define Z_FOR_EACH_SWALLOW_INDEX_FIXED_ARG(F, fixed_arg, index, arg) F(arg) #define Z_FOR_EACH_SWALLOW_INDEX(F, fixed_arg, index, arg) F(arg, fixed_arg) +/* Internal macros used by FOR_EACH_NESTED */ +#define _Z_FEN_1(F, term, ...) F(GET_ARG_N(1, __VA_ARGS__), term) +#define _Z_FEN_2(F, term, ...) F(GET_ARG_N(2, __VA_ARGS__), \ + _Z_FEN_1(F, term, __VA_ARGS__)) +#define _Z_FEN_3(F, term, ...) F(GET_ARG_N(3, __VA_ARGS__), \ + _Z_FEN_2(F, term, __VA_ARGS__)) +#define _Z_FEN_4(F, term, ...) F(GET_ARG_N(4, __VA_ARGS__), \ + _Z_FEN_3(F, term, __VA_ARGS__)) +#define _Z_FEN_5(F, term, ...) F(GET_ARG_N(5, __VA_ARGS__), \ + _Z_FEN_4(F, term, __VA_ARGS__)) +#define _Z_FEN_6(F, term, ...) F(GET_ARG_N(6, __VA_ARGS__), \ + _Z_FEN_5(F, term, __VA_ARGS__)) +#define _Z_FEN_7(F, term, ...) F(GET_ARG_N(7, __VA_ARGS__), \ + _Z_FEN_6(F, term, __VA_ARGS__)) +#define _Z_FEN_8(F, term, ...) F(GET_ARG_N(8, __VA_ARGS__), \ + _Z_FEN_7(F, term, __VA_ARGS__)) +#define _Z_FEN_9(F, term, ...) F(GET_ARG_N(9, __VA_ARGS__), \ + _Z_FEN_8(F, term, __VA_ARGS__)) +#define _Z_FEN_10(F, term, ...) F(GET_ARG_N(10, __VA_ARGS__), \ + _Z_FEN_9(F, term, __VA_ARGS__)) +#define _Z_FEN_11(F, term, ...) F(GET_ARG_N(11, __VA_ARGS__), \ + _Z_FEN_10(F, term, __VA_ARGS__)) +#define _Z_FEN_12(F, term, ...) F(GET_ARG_N(12, __VA_ARGS__), \ + _Z_FEN_11(F, term, __VA_ARGS__)) +#define _Z_FEN_13(F, term, ...) F(GET_ARG_N(13, __VA_ARGS__), \ + _Z_FEN_12(F, term, __VA_ARGS__)) +#define _Z_FEN_14(F, term, ...) F(GET_ARG_N(14, __VA_ARGS__), \ + _Z_FEN_13(F, term, __VA_ARGS__)) +#define _Z_FEN_15(F, term, ...) F(GET_ARG_N(15, __VA_ARGS__), \ + _Z_FEN_14(F, term, __VA_ARGS__)) +#define _Z_FEN_16(F, term, ...) F(GET_ARG_N(16, __VA_ARGS__), \ + _Z_FEN_15(F, term, __VA_ARGS__)) + +#define _Z_FOR_EACH_NESTED(F, term, ...) \ + UTIL_CAT(_Z_FEN_, NUM_VA_ARGS_LESS_1(__VA_ARGS__, _)) \ + (F, term, __VA_ARGS__) + /* This is a workaround to enable mixing GET_ARG_N with FOR_EACH macros. If * same UTIL_EVAL macro is used then macro is incorrectly resolved. */ diff --git a/include/sys/util_macro.h b/include/sys/util_macro.h index 06315da491b28..315c1b55070e9 100644 --- a/include/sys/util_macro.h +++ b/include/sys/util_macro.h @@ -526,6 +526,35 @@ extern "C" { 0, Z_FOR_EACH_SWALLOW_NOTHING, sep, \ F, fixed_arg, __VA_ARGS__) +/** + * @brief Call a macro @p F on each provided argument in a nested fashion + * + * For each argument provided evaluates a two input macro where the second + * parameter is the result of the evaluation on the previous argument. + * + * Example: + * + * // Find the smallest number in a list + * FOR_EACH_NESTED(MIN, INT_MAX, 4, 5, 6); + * + * This expands to: + * + * MIN(6, MIN(5, MIN(4, INT_MAX))) + * + * @param F Macro to invoke. + * @param term Final argument provided to the last invocation of @p F. + * @param ... Variable argument list. + * @return Result of macro invocation, or @p term if argument list is empty. + */ +#define FOR_EACH_NESTED(F, term, ...) \ + COND_CODE_0( \ + /* are there zero non-empty arguments ? */ \ + NUM_VA_ARGS_LESS_1(LIST_DROP_EMPTY(__VA_ARGS__, _)), \ + /* if so, expand to term */ \ + (term), \ + /* otherwise, expand to: */ \ + (_Z_FOR_EACH_NESTED(F, term, LIST_DROP_EMPTY(__VA_ARGS__)))) + /** * @brief Number of arguments in the variable arguments list minus one. * diff --git a/samples/application_development/linker_regions/CMakeLists.txt b/samples/application_development/linker_regions/CMakeLists.txt new file mode 100644 index 0000000000000..fb89e6eb8b8f1 --- /dev/null +++ b/samples/application_development/linker_regions/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(linker_regions) + +zephyr_linker_sources(SECTIONS partition_first.ld) +zephyr_linker_sources_ifdef(SECOND_REGION SECTIONS partition_second.ld) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/application_development/linker_regions/README.rst b/samples/application_development/linker_regions/README.rst new file mode 100644 index 0000000000000..c2cbdd465e93c --- /dev/null +++ b/samples/application_development/linker_regions/README.rst @@ -0,0 +1,23 @@ +.. _linker_regions: + +Linker Regions +############## + +Overview +******** + +A sample that demonstrates using devicetree to define linker +memory regions. These memory regions allow constants to be +placed at desired memory addresses in a portable manner. + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/application_development/linker_regions + :host-os: unix + :board: qemu_cortex_m3 + :goals: run + :compact: diff --git a/samples/application_development/linker_regions/boards/qemu_cortex_m3.overlay b/samples/application_development/linker_regions/boards/qemu_cortex_m3.overlay new file mode 100644 index 0000000000000..7418978d303a1 --- /dev/null +++ b/samples/application_development/linker_regions/boards/qemu_cortex_m3.overlay @@ -0,0 +1,20 @@ + /* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + first_partition: partition@38000 { + label = "first_region"; + reg = <0x00038000 0x00008000>; + linker-region; + }; + }; +}; diff --git a/samples/application_development/linker_regions/boards/qemu_cortex_m3_multiple.overlay b/samples/application_development/linker_regions/boards/qemu_cortex_m3_multiple.overlay new file mode 100644 index 0000000000000..c7a5bb65e4d35 --- /dev/null +++ b/samples/application_development/linker_regions/boards/qemu_cortex_m3_multiple.overlay @@ -0,0 +1,29 @@ + /* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Regions are unordered to show the correct selection of + * ROM bounds. + */ + first_partition: partition@3c000 { + label = "first_region"; + reg = <0x0003c000 0x00004000>; + linker-region; + }; + + second_partition: partition@38000 { + label = "second_region"; + reg = <0x00038000 0x00004000>; + linker-region; + }; + }; +}; diff --git a/samples/application_development/linker_regions/partition_first.ld b/samples/application_development/linker_regions/partition_first.ld new file mode 100644 index 0000000000000..212422e14f855 --- /dev/null +++ b/samples/application_development/linker_regions/partition_first.ld @@ -0,0 +1,17 @@ + /* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Link ".first_section" into the first partition */ +GROUP_START(first_region) + +SECTION_DATA_PROLOGUE(first_section,,) +{ + KEEP(*(".first_section")); + KEEP(*(".first_section.*")); +} GROUP_LINK_IN(first_region) + +GROUP_END(first_region) diff --git a/samples/application_development/linker_regions/partition_second.ld b/samples/application_development/linker_regions/partition_second.ld new file mode 100644 index 0000000000000..f20e7b8582c8b --- /dev/null +++ b/samples/application_development/linker_regions/partition_second.ld @@ -0,0 +1,17 @@ + /* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Link ".second_section" into the second partition */ +GROUP_START(second_region) + +SECTION_DATA_PROLOGUE(second_section,,) +{ + KEEP(*(".second_section")); + KEEP(*(".second_section.*")); +} GROUP_LINK_IN(second_region) + +GROUP_END(second_region) diff --git a/samples/application_development/linker_regions/prj.conf b/samples/application_development/linker_regions/prj.conf new file mode 100644 index 0000000000000..b2a4ba591044e --- /dev/null +++ b/samples/application_development/linker_regions/prj.conf @@ -0,0 +1 @@ +# nothing here diff --git a/samples/application_development/linker_regions/sample.yaml b/samples/application_development/linker_regions/sample.yaml new file mode 100644 index 0000000000000..0563bed71e0a8 --- /dev/null +++ b/samples/application_development/linker_regions/sample.yaml @@ -0,0 +1,21 @@ +sample: + name: Linker regions +tests: + sample.app_dev.linker_regions: + tags: linker + harness: console + harness_config: + type: one_line + regex: + - "First @ 0x38000: 0xdeadbeef" + platform_allow: qemu_cortex_m3 + sample.app_dev.linker_regions_multiple: + tags: linker + harness: console + harness_config: + type: multi_line + regex: + - "First @ 0x3c000: 0xdeadbeef" + - "Second @ 0x38000: 0xf00dcafe" + platform_allow: qemu_cortex_m3 + extra_args: SECOND_REGION DTC_OVERLAY_FILE=boards/qemu_cortex_m3_multiple.overlay diff --git a/samples/application_development/linker_regions/src/main.c b/samples/application_development/linker_regions/src/main.c new file mode 100644 index 0000000000000..b17b102c7c06a --- /dev/null +++ b/samples/application_development/linker_regions/src/main.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define IN_SECTION(s) __attribute__((__section__(s))) + +#if DT_HAS_FIXED_PARTITION_LABEL(first_region) +uint32_t first_variable IN_SECTION(".first_section") = 0xdeadbeef; +#endif /* DT_HAS_FIXED_PARTITION_LABEL(first_region) */ + +#if DT_HAS_FIXED_PARTITION_LABEL(second_region) +uint32_t second_variable IN_SECTION(".second_section") = 0xf00dcafe; +#endif /* DT_HAS_FIXED_PARTITION_LABEL(second_region) */ + +void main(void) +{ +#if DT_HAS_FIXED_PARTITION_LABEL(first_region) + printk("First @ %p: 0x%x\n", &first_variable, first_variable); +#endif /* DT_HAS_FIXED_PARTITION_LABEL(first_region) */ + +#if DT_HAS_FIXED_PARTITION_LABEL(second_region) + printk("Second @ %p: 0x%x\n", &second_variable, second_variable); +#endif /* DT_HAS_FIXED_PARTITION_LABEL(second_region) */ +} diff --git a/tests/unit/util/test.inc b/tests/unit/util/test.inc index a318d252e17ef..ea00d262f4192 100644 --- a/tests/unit/util/test.inc +++ b/tests/unit/util/test.inc @@ -346,6 +346,24 @@ static void test_FOR_EACH_IDX_FIXED_ARG(void) zassert_equal(a2, 3, "Unexpected value %d", a2); } +static void test_FOR_EACH_NESTED(void) +{ + #undef FOO + /* MAX operation */ + #define FOO(a, b) (((a) > (b)) ? (a) : (b)) + + zassert_equal(FOR_EACH_NESTED(FOO, 10), 10, + "Empty invocation failed"); + zassert_equal(FOR_EACH_NESTED(FOO, 10, 1, 2, 3), 10, + "Term condition not selected"); + zassert_equal(FOR_EACH_NESTED(FOO, 10, 20, 2, 3), 20, + "Largest value not selected"); + zassert_equal(FOR_EACH_NESTED(FOO, 10, 1, 20, 3), 20, + "Largest value not selected"); + zassert_equal(FOR_EACH_NESTED(FOO, 10, 1, 2, 20), 20, + "Largest value not selected"); +} + static void test_IS_EMPTY(void) { #define test_IS_EMPTY_REAL_EMPTY @@ -465,6 +483,7 @@ void test_cc(void) ztest_unit_test(test_FOR_EACH_FIXED_ARG), ztest_unit_test(test_FOR_EACH_IDX), ztest_unit_test(test_FOR_EACH_IDX_FIXED_ARG), + ztest_unit_test(test_FOR_EACH_NESTED), ztest_unit_test(test_IS_EMPTY), ztest_unit_test(test_LIST_DROP_EMPTY), ztest_unit_test(test_nested_FOR_EACH),