From 2ade22ff858231284c2a52bec0bcd090a8ed418a Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 22 May 2025 15:04:17 +0200 Subject: [PATCH 1/9] feat(esp_system): add mutable markers to sections.ld.in templates Entity mappings for immutable libraries are placed in the existing `mapping[target]` marker, while those for mutable libraries are placed in the new `mutable[target]` marker, which comes after the `mapping` marker. Additionally, include padding after the input sections of mutable libraries in the default data and text output sections, providing a headroom for the mutable libraries to grow. Padding is currently not added, for example, in the `.iram0.data` output section, which is not expected to change frequently. Padding for other mutable input sections may be added in the future. Signed-off-by: Frantisek Hrbata --- components/esp_system/ld/esp32/sections.ld.in | 57 ++++++++++++ .../esp_system/ld/esp32c2/sections.ld.in | 50 ++++++++++ .../esp_system/ld/esp32c3/sections.ld.in | 53 +++++++++++ .../esp_system/ld/esp32c5/sections.ld.in | 56 ++++++++++++ .../esp_system/ld/esp32c6/sections.ld.in | 52 +++++++++++ .../esp_system/ld/esp32c61/sections.ld.in | 40 ++++++++ .../esp_system/ld/esp32h2/sections.ld.in | 52 +++++++++++ .../esp_system/ld/esp32h21/sections.ld.in | 52 +++++++++++ .../esp_system/ld/esp32h4/sections.ld.in | 36 ++++++++ .../esp_system/ld/esp32p4/sections.ld.in | 91 +++++++++++++++++++ .../esp_system/ld/esp32s2/sections.ld.in | 48 ++++++++++ .../esp_system/ld/esp32s3/sections.ld.in | 56 ++++++++++++ components/esp_system/ld/ld.common | 20 ++++ 13 files changed, 663 insertions(+) diff --git a/components/esp_system/ld/esp32/sections.ld.in b/components/esp_system/ld/esp32/sections.ld.in index 22b76e32b2..26966a5003 100644 --- a/components/esp_system/ld/esp32/sections.ld.in +++ b/components/esp_system/ld/esp32/sections.ld.in @@ -21,6 +21,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.literal .text .literal.* .text.*) _rtc_text_end = ABSOLUTE(.); @@ -51,6 +55,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -70,6 +78,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.*) _rtc_data_end = ABSOLUTE(.); @@ -85,6 +97,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > rtc_data_location @@ -224,6 +240,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > iram0_0_seg .dram0.data : @@ -239,6 +260,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > dram0_0_seg @@ -277,6 +303,10 @@ SECTIONS mapping[extern_ram] + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > extern_ram_seg @@ -289,8 +319,13 @@ SECTIONS * ldgen places all bss-related data to mapping[dram0_bss] * (See components/esp_system/app.lf). */ + mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > dram0_0_seg @@ -377,6 +412,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ @@ -411,6 +451,10 @@ SECTIONS _rodata_reserved_end = ABSOLUTE(.); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg .flash.text : @@ -425,6 +469,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.warning) *(.gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) @@ -466,6 +515,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > iram0_0_seg @@ -475,6 +528,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, _iram_end) } > iram0_0_seg diff --git a/components/esp_system/ld/esp32c2/sections.ld.in b/components/esp_system/ld/esp32c2/sections.ld.in index a905dde289..044a96ca45 100644 --- a/components/esp_system/ld/esp32c2/sections.ld.in +++ b/components/esp_system/ld/esp32c2/sections.ld.in @@ -24,6 +24,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > iram0_0_seg /** @@ -42,7 +47,12 @@ SECTIONS .dram0.bt.data : { _data_start = ABSOLUTE(.); + mapping[dram0_bt_data] + + /* mutable libs begin */ + mutable[dram0_bt_data] + /* mutable libs end */ } > dram0_0_seg .dram0.bt.bss (NOLOAD) : @@ -51,6 +61,10 @@ SECTIONS mapping[dram0_bt_bss] + /* mutable libs begin */ + mutable[dram0_bt_bss] + /* mutable libs end */ + _bss_bt_end = ABSOLUTE(.); } > dram0_0_seg @@ -67,6 +81,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > dram0_0_seg @@ -94,8 +113,13 @@ SECTIONS * ldgen places all bss-related data to mapping[dram0_bss] * (See components/esp_system/app.lf). */ + mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > dram0_0_seg @@ -114,6 +138,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -216,6 +245,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -287,6 +321,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of IRAM code segment */ @@ -301,6 +339,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > iram0_0_seg @@ -310,6 +352,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); } > iram0_0_seg @@ -324,6 +370,10 @@ SECTIONS mapping[iram0_bt_text] + /* mutable libs begin */ + mutable[iram0_bt_text] + /* mutable libs end */ + ALIGNED_SYMBOL(16, _iram_end) } > iram0_0_seg diff --git a/components/esp_system/ld/esp32c3/sections.ld.in b/components/esp_system/ld/esp32c3/sections.ld.in index e18a528dd5..fba4a6f40f 100644 --- a/components/esp_system/ld/esp32c3/sections.ld.in +++ b/components/esp_system/ld/esp32c3/sections.ld.in @@ -23,6 +23,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -46,6 +50,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -63,6 +71,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -78,6 +90,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > rtc_data_location @@ -164,6 +180,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > iram0_0_seg /** @@ -189,6 +210,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > dram0_0_seg @@ -216,8 +242,13 @@ SECTIONS * ldgen places all bss-related data to mapping[dram0_bss] * (See components/esp_system/app.lf). */ + mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > dram0_0_seg @@ -236,6 +267,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -338,6 +374,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -409,6 +450,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of IRAM code segment */ @@ -430,6 +475,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > iram0_0_seg @@ -439,6 +488,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) } > iram0_0_seg diff --git a/components/esp_system/ld/esp32c5/sections.ld.in b/components/esp_system/ld/esp32c5/sections.ld.in index 43be52efc7..1b74d282bc 100644 --- a/components/esp_system/ld/esp32c5/sections.ld.in +++ b/components/esp_system/ld/esp32c5/sections.ld.in @@ -28,6 +28,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -48,6 +52,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -65,6 +73,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -80,6 +92,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > lp_ram_seg @@ -179,6 +195,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -196,6 +217,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -205,6 +230,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) } > sram_seg @@ -232,6 +261,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -261,6 +295,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -278,6 +316,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -387,6 +430,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -466,6 +514,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ @@ -494,6 +546,10 @@ SECTIONS mapping[extern_ram] + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > extern_ram_seg #endif //CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY diff --git a/components/esp_system/ld/esp32c6/sections.ld.in b/components/esp_system/ld/esp32c6/sections.ld.in index 01edda27ee..b1384433e3 100644 --- a/components/esp_system/ld/esp32c6/sections.ld.in +++ b/components/esp_system/ld/esp32c6/sections.ld.in @@ -28,6 +28,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -48,6 +52,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -65,6 +73,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -80,6 +92,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > lp_ram_seg @@ -192,6 +208,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -209,6 +230,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -218,6 +243,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) @@ -237,6 +266,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -266,6 +300,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -281,6 +319,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -383,6 +426,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -454,6 +502,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ diff --git a/components/esp_system/ld/esp32c61/sections.ld.in b/components/esp_system/ld/esp32c61/sections.ld.in index db2675780b..628bb3b900 100644 --- a/components/esp_system/ld/esp32c61/sections.ld.in +++ b/components/esp_system/ld/esp32c61/sections.ld.in @@ -37,6 +37,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -54,6 +59,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -63,6 +72,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) } > sram_seg @@ -90,6 +103,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -119,6 +137,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -136,6 +158,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -246,6 +273,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -325,6 +357,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ @@ -353,6 +389,10 @@ SECTIONS mapping[extern_ram] + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > extern_ram_seg #endif //CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY diff --git a/components/esp_system/ld/esp32h2/sections.ld.in b/components/esp_system/ld/esp32h2/sections.ld.in index 0fd235f04d..4d4c5293c8 100644 --- a/components/esp_system/ld/esp32h2/sections.ld.in +++ b/components/esp_system/ld/esp32h2/sections.ld.in @@ -28,6 +28,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -48,6 +52,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -65,6 +73,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -80,6 +92,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > lp_ram_seg @@ -194,6 +210,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -211,6 +232,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -220,6 +245,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) @@ -239,6 +268,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -268,6 +302,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -283,6 +321,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -385,6 +428,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -456,6 +504,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ diff --git a/components/esp_system/ld/esp32h21/sections.ld.in b/components/esp_system/ld/esp32h21/sections.ld.in index d2227359f6..1d6ec0fc2e 100644 --- a/components/esp_system/ld/esp32h21/sections.ld.in +++ b/components/esp_system/ld/esp32h21/sections.ld.in @@ -30,6 +30,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -50,6 +54,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -67,6 +75,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -82,6 +94,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > lp_ram_seg @@ -183,6 +199,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -200,6 +221,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -209,6 +234,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) @@ -228,6 +257,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -257,6 +291,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -272,6 +310,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -374,6 +417,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -445,6 +493,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ diff --git a/components/esp_system/ld/esp32h4/sections.ld.in b/components/esp_system/ld/esp32h4/sections.ld.in index fa715e3a23..bc355709e3 100644 --- a/components/esp_system/ld/esp32h4/sections.ld.in +++ b/components/esp_system/ld/esp32h4/sections.ld.in @@ -27,6 +27,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_seg /* Marks the end of IRAM code segment */ @@ -44,6 +49,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_seg @@ -53,6 +62,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) } > sram_seg @@ -71,6 +84,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > sram_seg @@ -100,6 +118,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > sram_seg @@ -117,6 +139,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -219,6 +246,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > default_rodata_seg @@ -290,6 +322,10 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /* Marks the end of data, bss and possibly rodata */ diff --git a/components/esp_system/ld/esp32p4/sections.ld.in b/components/esp_system/ld/esp32p4/sections.ld.in index be51ae5318..2e3d85f6bf 100644 --- a/components/esp_system/ld/esp32p4/sections.ld.in +++ b/components/esp_system/ld/esp32p4/sections.ld.in @@ -26,8 +26,13 @@ SECTIONS ALIGNED_SYMBOL(_esp_pmp_align_size, _rtc_text_start) arrays[rtc_text] + mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.text .text.*) *(.rtc_text_end_test) @@ -47,8 +52,13 @@ SECTIONS ALIGNED_SYMBOL(4, _rtc_force_fast_start) arrays[rtc_force_fast] + mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -65,8 +75,13 @@ SECTIONS _rtc_data_start = ABSOLUTE(.); arrays[rtc_data] + mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .srodata.*) _rtc_data_end = ABSOLUTE(.); @@ -81,8 +96,13 @@ SECTIONS *rtc_wake_stub*.*(COMMON) arrays[rtc_bss] + mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > lp_ram_seg @@ -163,8 +183,13 @@ SECTIONS _tcm_text_start = ABSOLUTE(.); arrays[tcm_text] + mapping[tcm_text] + /* mutable libs begin */ + mutable[tcm_text] + /* mutable libs end */ + _tcm_text_end = ABSOLUTE(.); } > tcm_idram_seg @@ -173,8 +198,13 @@ SECTIONS _tcm_data_start = ABSOLUTE(.); arrays[tcm_data] + mapping[tcm_data] + /* mutable libs begin */ + mutable[tcm_data] + /* mutable libs end */ + _tcm_data_end = ABSOLUTE(.); } > tcm_idram_seg @@ -190,8 +220,14 @@ SECTIONS _iram_text_start = ABSOLUTE(.); arrays[iram0_text] + mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > sram_low /* Marks the end of IRAM code segment */ @@ -208,8 +244,13 @@ SECTIONS ALIGNED_SYMBOL(16, _iram_data_start) arrays[iram0_data] + mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + _iram_data_end = ABSOLUTE(.); } > sram_low @@ -218,8 +259,13 @@ SECTIONS ALIGNED_SYMBOL(16, _iram_bss_start) arrays[iram0_bss] + mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(16, _iram_end) @@ -238,8 +284,14 @@ SECTIONS *(.jcr) arrays[dram0_data] + mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end_low = ABSOLUTE(.); } > sram_low @@ -249,6 +301,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end_high = ABSOLUTE(.); } > sram_high @@ -278,8 +335,14 @@ SECTIONS _text_start = ABSOLUTE(.); arrays[flash_text] + mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.linkonce.t.*) *(.gnu.warning) @@ -360,6 +423,7 @@ SECTIONS _flash_rodata_start = ABSOLUTE(.); arrays[flash_rodata] + mapping[flash_rodata] *(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */ @@ -396,6 +460,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); . = ALIGN(ALIGNOF(SECTION_AFTER_FLASH_RODATA)); } > rodata_seg_low @@ -475,7 +544,13 @@ SECTIONS _rodata_reserved_end = ADDR(.flash.tbss); arrays[rodata_noload] + mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ + } > rodata_seg_low #if CONFIG_SPIRAM_XIP_FROM_PSRAM @@ -495,8 +570,15 @@ SECTIONS .ext_ram.bss (NOLOAD) : { _ext_ram_bss_start = ABSOLUTE(.); + arrays[extern_ram] + mapping[extern_ram] + + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > ext_ram_seg #endif //CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY @@ -525,8 +607,13 @@ SECTIONS * (See components/esp_system/app.lf). */ arrays[dram0_bss] + mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _bss_end_low) } > sram_low @@ -540,6 +627,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _bss_end_high) } > sram_high diff --git a/components/esp_system/ld/esp32s2/sections.ld.in b/components/esp_system/ld/esp32s2/sections.ld.in index 1832712fd4..7f90596d34 100644 --- a/components/esp_system/ld/esp32s2/sections.ld.in +++ b/components/esp_system/ld/esp32s2/sections.ld.in @@ -30,6 +30,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.literal .text .literal.* .text.*) HIDDEN(_rtc_code_end = .); @@ -65,6 +69,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -84,6 +92,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.*) _rtc_data_end = ABSOLUTE(.); @@ -99,6 +111,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > rtc_data_location @@ -213,6 +229,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + /* Padding for possible CPU prefetch + alignment for PMS split lines */ . += _esp_memprot_prefetch_pad_size; . = ALIGN(_esp_memprot_align_size); @@ -241,6 +262,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > dram0_0_seg @@ -267,6 +293,10 @@ SECTIONS mapping[extern_ram] + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > extern_ram_seg #endif //CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY @@ -297,6 +327,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > dram0_0_seg @@ -380,6 +414,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ @@ -414,6 +453,10 @@ SECTIONS _rodata_reserved_end = ABSOLUTE(.); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg .flash.text : @@ -428,6 +471,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.warning) *(.gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) diff --git a/components/esp_system/ld/esp32s3/sections.ld.in b/components/esp_system/ld/esp32s3/sections.ld.in index 26abf1fa66..52a6e7c460 100644 --- a/components/esp_system/ld/esp32s3/sections.ld.in +++ b/components/esp_system/ld/esp32s3/sections.ld.in @@ -26,6 +26,10 @@ SECTIONS mapping[rtc_text] + /* mutable libs begin */ + mutable[rtc_text] + /* mutable libs end */ + *rtc_wake_stub*.*(.literal .text .literal.* .text.*) *(.rtc_text_end_test) @@ -49,6 +53,10 @@ SECTIONS mapping[rtc_force_fast] + /* mutable libs begin */ + mutable[rtc_force_fast] + /* mutable libs end */ + *(.rtc.force_fast .rtc.force_fast.*) ALIGNED_SYMBOL(4, _rtc_force_fast_end) @@ -68,6 +76,10 @@ SECTIONS mapping[rtc_data] + /* mutable libs begin */ + mutable[rtc_data] + /* mutable libs end */ + *rtc_wake_stub*.*(.data .rodata .data.* .rodata.*) _rtc_data_end = ABSOLUTE(.); @@ -83,6 +95,10 @@ SECTIONS mapping[rtc_bss] + /* mutable libs begin */ + mutable[rtc_bss] + /* mutable libs end */ + _rtc_bss_end = ABSOLUTE(.); } > rtc_data_location @@ -197,6 +213,11 @@ SECTIONS mapping[iram0_text] + /* mutable libs begin */ + mutable[iram0_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + } > iram0_0_seg /** @@ -221,6 +242,11 @@ SECTIONS mapping[dram0_data] + /* mutable libs begin */ + mutable[dram0_data] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _data_end = ABSOLUTE(.); } > dram0_0_seg @@ -250,6 +276,10 @@ SECTIONS */ mapping[dram0_bss] + /* mutable libs begin */ + mutable[dram0_bss] + /* mutable libs end */ + ALIGNED_SYMBOL(8, _bss_end) } > dram0_0_seg @@ -268,6 +298,11 @@ SECTIONS mapping[flash_text] + /* mutable libs begin */ + mutable[flash_text] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + *(.stub) *(.gnu.warning) *(.gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) @@ -391,6 +426,11 @@ SECTIONS KEEP (*(SORT_BY_INIT_PRIORITY(.esp_system_init_fn.*))) _esp_system_init_fn_array_end = ABSOLUTE(.); + /* mutable libs begin */ + mutable[flash_rodata] + FAST_REFLASHING_PADDING; + /* mutable libs end */ + _rodata_end = ABSOLUTE(.); /* Literals are also RO data. */ @@ -425,6 +465,10 @@ SECTIONS _rodata_reserved_end = ABSOLUTE(.); mapping[rodata_noload] + + /* mutable libs begin */ + mutable[rodata_noload] + /* mutable libs end */ } > default_rodata_seg /** @@ -446,6 +490,10 @@ SECTIONS mapping[extern_ram] + /* mutable libs begin */ + mutable[extern_ram] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _ext_ram_bss_end) } > extern_ram_seg #endif //CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY @@ -484,6 +532,10 @@ SECTIONS mapping[iram0_data] + /* mutable libs begin */ + mutable[iram0_data] + /* mutable libs end */ + ALIGNED_SYMBOL(4, _iram_data_end) } > iram0_0_seg @@ -493,6 +545,10 @@ SECTIONS mapping[iram0_bss] + /* mutable libs begin */ + mutable[iram0_bss] + /* mutable libs end */ + _iram_bss_end = ABSOLUTE(.); ALIGNED_SYMBOL(4, _iram_end) } > iram0_0_seg diff --git a/components/esp_system/ld/ld.common b/components/esp_system/ld/ld.common index 04401fd24b..58ac0fbb8d 100644 --- a/components/esp_system/ld/ld.common +++ b/components/esp_system/ld/ld.common @@ -94,3 +94,23 @@ ASSERT((ADDR(NEXT_SECTION) == ADDR(PREV_SECTION) + SIZEOF(PREV_SECTION)), \ #else #define SECTION_AFTER_FLASH_RODATA .flash.tdata #endif + +/* + * FAST_REFLASHING_PADDING + * + * Aligns the current location (.) to CONFIG_ESPTOOLPY_FAST_REFLASHING_PADDING. + * If more than half of the alignment block is already consumed, + * extra padding is applied to skip to the next boundary. + * For example, if the current location (.) is 0x1900 and the padding is 4 KB, + * the location will be moved to 0x3000 instead of 0x2000 because less than + * half of the current 4 KB block remains (0x2000 - 0x1900 < 2 KB). + */ +#if CONFIG_ESPTOOLPY_FAST_REFLASHING +#define FAST_REFLASHING_PADDING \ + . = ALIGN((. + ( \ + (CONFIG_ESPTOOLPY_FAST_REFLASHING_PADDING - (ALIGN(CONFIG_ESPTOOLPY_FAST_REFLASHING_PADDING) - .)) + \ + (CONFIG_ESPTOOLPY_FAST_REFLASHING_PADDING / 2) \ + )), CONFIG_ESPTOOLPY_FAST_REFLASHING_PADDING) +#else +#define FAST_REFLASHING_PADDING +#endif From 299e172fcacfd38b7d877a18a8d6ea1bee4f18fa Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 22 May 2025 14:17:32 +0200 Subject: [PATCH 2/9] feat(ldgen): place input sections from mutable libraries into mutable markers Introduce a new `--mutable-libraries-file` option that accepts a file containing the filenames of mutable libraries, each listed on a separate line. Mutable libraries are component libraries expected to change during development. In contrast, immutable component libraries are not expected to change. In the generated linker script, the input sections of mutable libraries are grouped together rather than being mixed with those of immutable libraries. The goal is to create large continuous areas in the ELF file's output sections that remain unchanged for immutable libraries during application recompilation, allowing these areas to be skipped during flashing. The build system identifies the mutable libraries and passes them to ldgen using the `--mutable-libraries-file` option. It maintains information about component sources, one of which is `project_components`. This source type identifies components that are directly related to the project being developed and are very likely to change. Mappings for mutable libraries are explicitly created for all sections in the default scheme. This happens before the entity `(archive:object_file:symbol/input_section)` node tree with placements is generated and is equivalent to having these mappings in the mapping linker fragment. All placements for mappings, whether newly added or already existing as defined in linker fragments, associated with mutable libraries are flagged as `mutable` in the entity node tree. This flag ensures that these placements are included in the final linker script. Currently, ldgen only emits placements that are either significant or forced. A placement is considered significant if, for example, it is not already covered by a placement in parent node. For instance, `*(.iram1 .iram1.*)` placement already includes `*libapp_trace.a:(.iram1 .iram1.*)`, so the latter is not emitted by default. The `mutable` flag ensures that placements for mutable libraries are emitted in the linker script and placed at dedicated location. The locations where placements for mutable libraries are specified in the linker script are identified by a new `mutable` marker, for example, `mutable[flash_text]`. The placements for immutable libraries remain in the existing `mapping` marker, for example, `mapping[flash_text]`. The `mutable` marker for each target is placed after the `mapping` marker. Signed-off-by: Frantisek Hrbata --- tools/ldgen/ldgen.py | 9 +- tools/ldgen/ldgen/generation.py | 122 ++++++++++++++++++++------- tools/ldgen/ldgen/linker_script.py | 10 ++- tools/ldgen/ldgen/output_commands.py | 9 +- 4 files changed, 115 insertions(+), 35 deletions(-) diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index 681020fa36..e13ad5deb7 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -62,6 +62,11 @@ def main(): type=argparse.FileType('r'), help='File that contains the list of libraries in the build') + argparser.add_argument( + '--mutable-libraries-file', + type=argparse.FileType('r'), + help='File that contains the list of mutable libraries in the build') + argparser.add_argument( '--output', '-o', help='Output linker script', @@ -104,6 +109,7 @@ def main(): input_file = args.input libraries_file = args.libraries_file + mutable_libraries_file = args.mutable_libraries_file or [] config_file = args.config output_path = args.output kconfig_file = args.kconfig @@ -132,7 +138,8 @@ def main(): dump.name = library sections_infos.add_sections_info(dump) - generation_model = Generation(check_mapping, check_mapping_exceptions) + mutable_libs = [lib.strip() for lib in mutable_libraries_file] + generation_model = Generation(check_mapping, check_mapping_exceptions, mutable_libs, args.debug) _update_environment(args) # assign args.env and args.env_file to os.environ diff --git a/tools/ldgen/ldgen/generation.py b/tools/ldgen/ldgen/generation.py index 945884981f..59a1491ec1 100644 --- a/tools/ldgen/ldgen/generation.py +++ b/tools/ldgen/ldgen/generation.py @@ -41,7 +41,7 @@ class Placement: for details). """ - def __init__(self, node, sections, target, flags, explicit, force=False, dryrun=False): + def __init__(self, node, sections, target, flags, explicit, force=False, dryrun=False, mutable=False): self.node = node self.sections = sections self.target = target @@ -57,6 +57,10 @@ class Placement: # fragment entry. self.explicit = explicit + # This placement is designated as mutable and should be assigned to a + # mutable target. + self.mutable = mutable + # Find basis placement. parent = node.parent candidate = None @@ -83,10 +87,16 @@ class Placement: # # Placement can also be a basis if it has flags # (self.flags) or its basis has flags (self.basis.flags) + # + # The mutable placement mark is used to place the placement within a + # designated area (MutableMarker) in the linker script. If the basis is + # also mutable, the placement is deemed insignificant to prevent adding + # unnecessary EXCLUDE_FILE and placement rules to the linker script. significant = (not self.basis or self.target != self.basis.target or (self.flags and not self.basis.flags) or (not self.flags and self.basis.flags) or + (self.mutable and not self.basis.mutable) or self.force) if significant and not self.explicit and not self.sections: @@ -112,7 +122,6 @@ class Placement: def add_subplacement(self, subplacement): self.subplacements.add(subplacement) - class EntityNode: """ Node in entity tree. An EntityNode @@ -196,6 +205,7 @@ class EntityNode: keep = False sort = None surround_type = [] + mutable = placement.mutable placement_flags = placement.flags if placement.flags is not None else [] @@ -212,9 +222,9 @@ class EntityNode: for flag in surround_type: if flag.pre: if isinstance(flag, Surround): - commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_start', tied)) + commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_start', tied, mutable=mutable)) else: # ALIGN - commands[placement.target].append(AlignAtAddress(flag.alignment, tied)) + commands[placement.target].append(AlignAtAddress(flag.alignment, tied, mutable=mutable)) # This is for expanded object node and symbol node placements without checking for # the type. @@ -222,7 +232,7 @@ class EntityNode: command_sections = sections if sections == placement_sections else placement_sections command = InputSectionDesc(placement.node.entity, command_sections, - [e.node.entity for e in placement.exclusions], keep, sort, tied) + [e.node.entity for e in placement.exclusions], keep, sort, tied, mutable=mutable) commands[placement.target].append(command) # Generate commands for intermediate, non-explicit exclusion placements here, @@ -230,34 +240,34 @@ class EntityNode: for subplacement in placement.subplacements: if not subplacement.flags and not subplacement.explicit: command = InputSectionDesc(subplacement.node.entity, subplacement.sections, - [e.node.entity for e in subplacement.exclusions], keep, sort, tied) + [e.node.entity for e in subplacement.exclusions], keep, sort, tied, mutable=mutable) commands[placement.target].append(command) for flag in surround_type: if flag.post: if isinstance(flag, Surround): - commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_end', tied)) + commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_end', tied, mutable=mutable)) else: # ALIGN - commands[placement.target].append(AlignAtAddress(flag.alignment, tied)) + commands[placement.target].append(AlignAtAddress(flag.alignment, tied, mutable=mutable)) return commands - def self_placement(self, sections, target, flags, explicit=True, force=False): - placement = Placement(self, sections, target, flags, explicit, force) + def self_placement(self, sections, target, flags, explicit=True, force=False, mutable=False): + placement = Placement(self, sections, target, flags, explicit, force=force, mutable=mutable) self.placements[sections] = placement return placement - def child_placement(self, entity, sections, target, flags, sections_db): + def child_placement(self, entity, sections, target, flags, mutable, sections_db): child = self.add_child(entity) - child.insert(entity, sections, target, flags, sections_db) + child.insert(entity, sections, target, flags, mutable, sections_db) - def insert(self, entity, sections, target, flags, sections_db): + def insert(self, entity, sections, target, flags, mutable, sections_db): if self.entity.specificity == entity.specificity: # Since specificities match, create the placement in this node. - self.self_placement(sections, target, flags) + self.self_placement(sections, target, flags, mutable=mutable) else: # If not, create a child node and try to create the placement there. - self.child_placement(entity, sections, target, flags, sections_db) + self.child_placement(entity, sections, target, flags, mutable, sections_db) def get_output_sections(self): return sorted(self.placements.keys(), key=lambda x: sorted(x)) # pylint: disable=W0108 @@ -298,9 +308,9 @@ class ObjectNode(EntityNode): self.entity = Entity(self.parent.name, self.name) self.subplacements = list() - def child_placement(self, entity, sections, target, flags, sections_db): + def child_placement(self, entity, sections, target, flags, mutable, sections_db): child = self.add_child(entity) - sym_placement = Placement(child, sections, target, flags, True, dryrun=True) + sym_placement = Placement(child, sections, target, flags, True, dryrun=True, mutable=mutable) # The basis placement for sym_placement can either be # an existing placement on this node, or nonexistent. @@ -331,7 +341,7 @@ class ObjectNode(EntityNode): obj_placement = self.placements[sections] except KeyError: # Create intermediate placement. - obj_placement = self.self_placement(sections, sym_placement.basis.target, None, False) + obj_placement = self.self_placement(sections, sym_placement.basis.target, None, False, mutable=mutable) if obj_placement.basis.flags: subplace = True @@ -383,9 +393,9 @@ class Generation: """ # Processed mapping, scheme and section entries - EntityMapping = namedtuple('EntityMapping', 'entity sections_group target flags') + EntityMapping = namedtuple('EntityMapping', 'entity sections_group target flags mutable') - def __init__(self, check_mappings=False, check_mapping_exceptions=None): + def __init__(self, check_mappings=False, check_mapping_exceptions=None, mutable_libs=None, debug=False): self.schemes = {} self.placements = {} self.mappings = {} @@ -397,6 +407,11 @@ class Generation: else: self.check_mapping_exceptions = [] + if mutable_libs is None: + self.mutable_libs = [] + else: + self.mutable_libs = mutable_libs + def _prepare_scheme_dictionary(self): scheme_dictionary = collections.defaultdict(dict) @@ -442,12 +457,51 @@ class Generation: return scheme_dictionary + def _get_section_strs(self, section): + s_list = [Sections.get_section_data_from_entry(s) for s in section.entries] + return frozenset([item for sublist in s_list for item in sublist]) + + def _prepare_mutable_entity_mappings(self, entity_mappings, scheme_dictionary, entities): + # Go through all mutable libraries, if any, and add entity mapping for + # each "default" section set for which the entity mapping does not yet + # exist. This adds mapping for every mutable library as if specified in + # the mapping fragment. All newly added or existing entity mappings + # related to mutable libraries are marked as mutable. This flag is used + # when the placement for it is created in the entity tree, ensuring the + # placement is forced to be emitted in the linker script and not + # considered non-significant. + + for archive in self.mutable_libs: + entity = Entity(archive, '*') + for target, sections in scheme_dictionary['default'].items(): + for section in sections: + sections_str = self._get_section_strs(section) + key = (entity, sections_str) + if entity_mappings.get(key): + # Mutable library already has entity mapping. + continue + + entity_mappings[key] = Generation.EntityMapping(entity, + sections_str, + target, + [], + True) + + for key, mapping in entity_mappings.items(): + (entity, sections, target, flags, mutable) = mapping + if entity.archive not in self.mutable_libs or mutable: + # The entity either does not belong to a mutable library or is + # an archive entity that has already been marked as mutable. + continue + # Set entity as mutable. + entity_mappings[key] = Generation.EntityMapping(entity, + sections, + target, + flags, + True) + def _prepare_entity_mappings(self, scheme_dictionary, entities): # Prepare entity mappings processed from mapping fragment entries. - def get_section_strs(section): - s_list = [Sections.get_section_data_from_entry(s) for s in section.entries] - return frozenset([item for sublist in s_list for item in sublist]) - entity_mappings = dict() for mapping in self.mappings.values(): @@ -488,7 +542,7 @@ class Generation: if (flag.section, flag.target) == (section.name, target): _flags.extend(flag.flags) - sections_str = get_section_strs(section) + sections_str = self._get_section_strs(section) key = (entity, sections_str) @@ -498,7 +552,11 @@ class Generation: existing = None if not existing: - entity_mappings[key] = Generation.EntityMapping(entity, sections_str, target, _flags) + entity_mappings[key] = Generation.EntityMapping(entity, + sections_str, + target, + _flags, + False) else: # Check for conflicts. if target != existing.target: @@ -511,12 +569,18 @@ class Generation: _flags.extend(existing.flags) entity_mappings[key] = Generation.EntityMapping(entity, sections_str, - target, _flags) + target, + _flags, + False) elif _flags == existing.flags: pass else: raise GenerationException('Conflicting flags specified.', mapping) + # Add new mappings for mutable libraries and modify existing ones if + # they pertain to them. + self._prepare_mutable_entity_mappings(entity_mappings, scheme_dictionary, entities) + # Sort the mappings by specificity, so as to simplify # insertion logic. res = list(entity_mappings.values()) @@ -528,9 +592,9 @@ class Generation: entity_mappings = self._prepare_entity_mappings(scheme_dictionary, entities) root_node = RootNode() for mapping in entity_mappings: - (entity, sections, target, flags) = mapping + (entity, sections, target, flags, mutable) = mapping try: - root_node.insert(entity, sections, target, flags, entities) + root_node.insert(entity, sections, target, flags, mutable, entities) except ValueError as e: raise GenerationException(str(e)) diff --git a/tools/ldgen/ldgen/linker_script.py b/tools/ldgen/ldgen/linker_script.py index c57188f418..c74e478c8c 100644 --- a/tools/ldgen/ldgen/linker_script.py +++ b/tools/ldgen/ldgen/linker_script.py @@ -24,6 +24,7 @@ class LinkerScript: MappingMarker = collections.namedtuple('MappingMarker', 'target indent rules') ArraysMarker = collections.namedtuple('ArraysMarker', 'target indent rules') + MutableMarker = collections.namedtuple('MutableMarker', 'target indent rules') def __init__(self, template_file): self.members = [] @@ -37,16 +38,19 @@ class LinkerScript: target = Fragment.IDENTIFIER pattern_mapping = White(' \t') + Suppress('mapping') + Suppress('[') + target + Suppress(']') pattern_arrays = White(' \t') + Suppress('arrays') + Suppress('[') + target + Suppress(']') + pattern_mutable = White(' \t') + Suppress('mutable') + Suppress('[') + target + Suppress(']') # Find the markers in the template file line by line. If line does not match marker grammar, # set it as a literal to be copied as is to the output file. for line in lines: parsed = False - for pattern in (pattern_arrays, pattern_mapping): + for pattern in (pattern_arrays, pattern_mapping, pattern_mutable): try: indent, target = pattern.parse_string(line) if pattern is pattern_arrays: marker = LinkerScript.ArraysMarker(target, indent, []) + elif pattern is pattern_mutable: + marker = LinkerScript.MutableMarker(target, indent, []) else: marker = LinkerScript.MappingMarker(target, indent, []) self.members.append(marker) @@ -65,8 +69,10 @@ class LinkerScript: if isinstance(member, self.ArraysMarker): rules = [x for x in mapping_rules[target] if x.tied] + elif isinstance(member, self.MutableMarker): + rules = [x for x in mapping_rules[target] if x.mutable and not x.tied] else: - rules = [x for x in mapping_rules[target] if not x.tied] + rules = [x for x in mapping_rules[target] if not x.tied and not x.mutable] member.rules.extend(rules) except KeyError: message = GenerationException.UNDEFINED_REFERENCE + " to target '" + target + "'." diff --git a/tools/ldgen/ldgen/output_commands.py b/tools/ldgen/ldgen/output_commands.py index a5c58b7a8c..c3d2ff0af6 100644 --- a/tools/ldgen/ldgen/output_commands.py +++ b/tools/ldgen/ldgen/output_commands.py @@ -19,9 +19,10 @@ class AlignAtAddress: command to be emitted. """ - def __init__(self, alignment, tied=False): + def __init__(self, alignment, tied=False, mutable=False): self.alignment = alignment self.tied = tied + self.mutable = mutable def __str__(self): return ('. = ALIGN(%d);' % self.alignment) @@ -43,9 +44,10 @@ class SymbolAtAddress: an InputSectionDesc. """ - def __init__(self, symbol, tied=False): + def __init__(self, symbol, tied=False, mutable=False): self.symbol = symbol self.tied = tied + self.mutable = mutable def __str__(self): return ('%s = ABSOLUTE(.);' % self.symbol) @@ -65,7 +67,7 @@ class InputSectionDesc: the emitted input section description. """ - def __init__(self, entity, sections, exclusions=None, keep=False, sort=None, tied=False): + def __init__(self, entity, sections, exclusions=None, keep=False, sort=None, tied=False, mutable=False): assert entity.specificity != Entity.Specificity.SYMBOL self.entity = entity @@ -83,6 +85,7 @@ class InputSectionDesc: self.keep = keep self.sort = sort self.tied = tied + self.mutable = mutable def __str__(self): sections_string = '' From 61193678e5b7ac6bd5f7d4a1f7732f00d81b304b Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 10 Jun 2025 09:33:28 +0200 Subject: [PATCH 3/9] feat(ldgen/test): add test class for mutable libraries Add basis tests for mutable libraries. Signed-off-by: Frantisek Hrbata --- tools/ldgen/test/test_generation.py | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tools/ldgen/test/test_generation.py b/tools/ldgen/test/test_generation.py index 209621d83d..916c9ddc62 100755 --- a/tools/ldgen/test/test_generation.py +++ b/tools/ldgen/test/test_generation.py @@ -214,6 +214,90 @@ entries: self.test_rule_generation_default() +class MutableMappingTest(GenerationTest): + # Collection of tests for mutable library mappings + + def dump_rules(self, rules): + # A simple helper for displaying the rules. It can be used to manually + # compare the actual and expected rules to identify any issues while + # preparing tests. + print() + for target, section_descs in rules.items(): + print(f'target: {target}') + for section_desc in section_descs: + print(f' {section_desc.entity}: {section_desc.sections}') + for exclusion in section_desc.exclusions: + print(f' {exclusion}') + + def add_mutable_libs_to_rules(self, libs, default_rules): + # Insert mutable lib rules and exclusions into default test rules. + rules = collections.defaultdict(list) + for target, section_descs in default_rules.items(): + for section_desc in section_descs: + # Duplicate the current section description. + section_desc_copy = InputSectionDesc(section_desc.entity, section_desc.sections) + for lib in libs: + # Exclude each mutable library from ROOT entity rule. + section_desc_copy.exclusions.add(Entity(lib)) + rules[target].append(section_desc_copy) + + # Add a rule for each mutable library and each ROOT entity rule section. + # This is done separately because the order is important. + for lib in libs: + for section_desc in section_descs: + rules[target].append(InputSectionDesc(Entity(lib), section_desc.sections)) + return rules + + def test_rule_generation_default(self): + # Verifies that the default rules are correctly generated from the + # default scheme and that mutable libraries are included in the + # generated rules. + mutable_libs = ['libmutable.a', 'libmutable2.a'] + + self.generation.mutable_libs = mutable_libs + + actual = self.generation.generate(self.entities, False) + default_rules = self.generate_default_rules() + expected = self.add_mutable_libs_to_rules(mutable_libs, default_rules) + + self.compare_rules(expected, actual) + self.generation.mutable_libs = [] + + def test_default_mapping_obj(self): + # Verifies that libfreertos.a:croutine entity is correctly excluded + # from the libfreertos.a entity and moved to iram and dram. + mapping = u""" +[mapping:test] +archive: libfreertos.a +entries: + croutine (noflash) +""" + mutable_libs = ['libfreertos.a'] + + self.generation.mutable_libs = mutable_libs + + self.add_fragments(mapping) + actual = self.generation.generate(self.entities, False) + default_rules = self.generate_default_rules() + expected = self.add_mutable_libs_to_rules(mutable_libs, default_rules) + + flash_text = expected['flash_text'] + flash_rodata = expected['flash_rodata'] + iram0_text = expected['iram0_text'] + dram0_data = expected['dram0_data'] + + # Add exclusions for the libfreertos.a:croutine entity into + # libfreertos.a entity in test and rodata. + flash_text[1].exclusions.add(CROUTINE) + flash_rodata[1].exclusions.add(CROUTINE) + + # Add rules for the libfreertos.a:croutine entity in iram and dram. + iram0_text.append(InputSectionDesc(CROUTINE, flash_text[1].sections)) + dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[1].sections)) + + self.compare_rules(expected, actual) + + class BasicTest(GenerationTest): # Test basic and fundamental interactions between typical # entries. From 3d081f0e23c6fbe28dbaec40ce1724eb24a604d0 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 10 Jun 2025 09:43:26 +0200 Subject: [PATCH 4/9] fix(ldgen/test): ruff formating changes and long lines Changes generated by pre-commit related to ruff-format and fixed long lines. Signed-off-by: Frantisek Hrbata --- tools/ldgen/test/test_generation.py | 240 ++++++++++++++++------------ 1 file changed, 139 insertions(+), 101 deletions(-) diff --git a/tools/ldgen/test/test_generation.py b/tools/ldgen/test/test_generation.py index 916c9ddc62..a14ffc9031 100755 --- a/tools/ldgen/test/test_generation.py +++ b/tools/ldgen/test/test_generation.py @@ -12,19 +12,27 @@ import unittest from io import StringIO try: - from ldgen.entity import Entity, EntityDB + from ldgen.entity import Entity + from ldgen.entity import EntityDB from ldgen.fragments import parse_fragment_file - from ldgen.generation import Generation, GenerationException + from ldgen.generation import Generation + from ldgen.generation import GenerationException from ldgen.linker_script import LinkerScript - from ldgen.output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress + from ldgen.output_commands import AlignAtAddress + from ldgen.output_commands import InputSectionDesc + from ldgen.output_commands import SymbolAtAddress from ldgen.sdkconfig import SDKConfig except ImportError: sys.path.append(os.path.dirname(os.path.dirname(__file__))) - from ldgen.entity import Entity, EntityDB + from ldgen.entity import Entity + from ldgen.entity import EntityDB from ldgen.fragments import parse_fragment_file - from ldgen.generation import Generation, GenerationException + from ldgen.generation import Generation + from ldgen.generation import GenerationException from ldgen.linker_script import LinkerScript - from ldgen.output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress + from ldgen.output_commands import AlignAtAddress + from ldgen.output_commands import InputSectionDesc + from ldgen.output_commands import SymbolAtAddress from ldgen.sdkconfig import SDKConfig ROOT = Entity('*') @@ -38,7 +46,6 @@ FREERTOS2 = Entity('libfreertos2.a') class GenerationTest(unittest.TestCase): - def setUp(self): self.generation = Generation() self.entities = None @@ -121,7 +128,6 @@ class GenerationTest(unittest.TestCase): class DefaultMappingTest(GenerationTest): - def test_rule_generation_default(self): # Checks that default rules are generated from # the default scheme properly and even if no mappings @@ -134,7 +140,7 @@ class DefaultMappingTest(GenerationTest): def test_default_mapping_lib(self): # Mapping a library with default mapping. This should not emit additional rules, # other than the default ones. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -146,7 +152,7 @@ entries: def test_default_mapping_obj(self): # Mapping an object with default mapping. This should not emit additional rules, # other than the default ones. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -158,7 +164,7 @@ entries: def test_default_mapping_symbol(self): # Mapping a symbol with default mapping. This should not emit additional rules, # other than the default ones. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -170,7 +176,7 @@ entries: def test_default_mapping_all(self): # Mapping a library, object, and symbol with default mapping. This should not emit additional rules, # other than the default ones. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -187,7 +193,7 @@ entries: # # This is a check needed to make sure generation does not generate # intermediate commands due to presence of symbol mapping. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -203,7 +209,7 @@ entries: # # This is a check needed to make sure generation does not generate # intermediate commands due to presence of symbol mapping. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -266,7 +272,7 @@ class MutableMappingTest(GenerationTest): def test_default_mapping_obj(self): # Verifies that libfreertos.a:croutine entity is correctly excluded # from the libfreertos.a entity and moved to iram and dram. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -314,7 +320,7 @@ class BasicTest(GenerationTest): # iram0_text # *(.iram ...) # *libfreertos.a(.literal ...) B - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -344,14 +350,14 @@ entries: # There should be exclusions in the default commands for flash_text and flash_rodata: # # flash_text - # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A + # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A # # Commands placing the entire library in iram, dram should be generated: # # iram0_text # *(.iram ...) - # *libfreertos.a:croutine(.literal ...) B - mapping = u""" + # *libfreertos.a:croutine(.literal ...) B + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -379,19 +385,20 @@ entries: def test_nondefault_mapping_symbol(self): # Test mapping entry different from default for symbol. - # There should be exclusions in the default commands for flash_text, as well as the implicit intermediate object command + # There should be exclusions in the default commands for flash_text, as + # well as the implicit intermediate object command # with an exclusion from default: # # flash_text - # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A - # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B + # *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B # # Commands placing the entire library in iram should be generated: # # iram0_text # *(.iram ...) - # *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) C - mapping = u""" + # *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) C + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -420,7 +427,9 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Input section commands in iram_text for #1 C - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -435,14 +444,14 @@ entries: # object file, including .debug, .comment and other input section. # # flash.rodata - # *((EXCLUDE_FILE(*libsoc.a:temperature_sensor_periph.*)) .rodata.* ...) A - # # *libsoc.a:temperature_sensor_periph.* X + # *((EXCLUDE_FILE(*libsoc.a:temperature_sensor_periph.*)) .rodata.* ...) A + # # *libsoc.a:temperature_sensor_periph.* X # # Commands placing the entire library in iram should be generated: # # dram0_data - # *libsoc.a:temperature_sensor_periph.*(.rodata.temperature_sensor_attribute) B - mapping = u""" + # *libsoc.a:temperature_sensor_periph.*(.rodata.temperature_sensor_attribute) B + mapping = """ [mapping:test] archive: libsoc.a entries: @@ -460,14 +469,15 @@ entries: flash_rodata[0].exclusions.add(TEMPERATURE_SENSOR_PERIPH) # Input section commands in dram0_data for #1 B - dram0_data.append(InputSectionDesc(TEMPERATURE_SENSOR_PERIPH, - set(['.rodata.temperature_sensor_attributes']), - [])) + dram0_data.append( + InputSectionDesc(TEMPERATURE_SENSOR_PERIPH, set(['.rodata.temperature_sensor_attributes']), []) + ) self.compare_rules(expected, actual) def test_default_symbol_nondefault_lib(self): - # Test default symbol mapping with different lib mapping. This should create an implicit intermediate object command. + # Test default symbol mapping with different lib mapping. This should + # create an implicit intermediate object command. # The significant targets are flash_text, flash_rodata, iram0_text, dram0_data. # # flash_text @@ -487,7 +497,7 @@ entries: # libfreertos.a ( .rodata ...) C.2 # # Only default commands are in the other targets. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -524,7 +534,9 @@ entries: iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Command for #2 B - flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + flash_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -549,7 +561,7 @@ entries: # *libfreertos.a:croutine(.rodata ....) C.2 # # Only default commands are in the other targets - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -583,7 +595,9 @@ entries: dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, [])) # Command for #2 B - flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + flash_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -616,7 +630,7 @@ entries: # libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .rodata ...) C # # For the other targets only the default commands should be present. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -659,14 +673,16 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Command for #3 D - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) def test_nondefault_but_same_lib_and_obj(self): # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping # to #1. Output is similar to test_different_mapping_lib. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -678,7 +694,7 @@ entries: def test_nondefault_but_same_lib_and_sym(self): # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping # to #1. Output is similar to test_different_mapping_lib. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -690,7 +706,7 @@ entries: def test_nondefault_but_same_obj_and_sym(self): # Commands should not be generated for #2, since it does similar mapping # to #1. Output is similar to test_different_mapping_obj. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -711,7 +727,7 @@ entries: # iram0_text # # - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -743,8 +759,12 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Commands for #1 & 2 - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])) - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []) + ) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -755,7 +775,7 @@ entries: # iram0_text # * (.custom_section) A # * (.iram .iram.*) - mapping = u""" + mapping = """ [sections:custom_section] entries: .custom_section @@ -784,7 +804,6 @@ entries: class AdvancedTest(GenerationTest): - # Test valid but quirky cases, corner cases, failure cases, and # cases involving interaction between schemes, other mapping # fragments. @@ -811,7 +830,7 @@ class AdvancedTest(GenerationTest): # *(.data ..) # *(.dram ...) # *libfreertos.a:croutine(.rodata .rodata.*) D - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -852,20 +871,20 @@ entries: # This operation should succeed with the following commands: # # flash_text - # *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A + # *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A # # flash_rodata - # *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B + # *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B # # iram0_text # *(.iram ...) - # *libfreertos.a:croutine(.text .text.* ...) C + # *libfreertos.a:croutine(.text .text.* ...) C # # dram0_data # *(.data ..) # *(.dram ...) - # *libfreertos.a:croutine(.rodata .rodata.*) D - mapping = u""" + # *libfreertos.a:croutine(.rodata .rodata.*) D + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -902,7 +921,7 @@ entries: # noflash = text -> iram0_text, rodata -> dram0_data # # This operation should fail. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -921,7 +940,7 @@ entries: # noflash = .text -> iram0_text, .rodata -> dram0_data # # This operation should fail. - mapping = u""" + mapping = """ [sections:custom_text] entries: .text+ @@ -945,42 +964,42 @@ entries: # using another. Another object and symbol is mapped the other way around. # # flash_text - # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...) A, B + # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...) A, B # # flash_rodata - # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...) A, B + # *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...) A, B # # dram0_data - # *(EXCLUDE_FILES(libfreertos.a:timers) .data ..) B + # *(EXCLUDE_FILES(libfreertos.a:timers) .data ..) B # *(.dram ...) - # *libfreertos.a:croutine(.rodata .rodata.*) C - # *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...) E + # *libfreertos.a:croutine(.rodata .rodata.*) C + # *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...) E # # dram0_bss - # *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...) B - # *(EXCLUDE_FILE(libfreertos.a:timers) COMMON) B + # *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...) B + # *(EXCLUDE_FILE(libfreertos.a:timers) COMMON) B # # iram0_text # *(.iram ...) - # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C - # *libfreertos.a:timers(.literal .literal.prvProcessReceivedCommands ...) E + # *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C + # *libfreertos.a:timers(.literal .literal.prvProcessReceivedCommands ...) E # # rtc_text # *(rtc.text .rtc.literal) - # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) F - # libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...) D.2 + # libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) F + # libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...) D.2 # # rtc_data # *(rtc.data) # *(rtc.rodata) - # libfreertos.a:timers (.data .data.*) D - # libfreertos.a:timers (.rodata ...) D.2 + # libfreertos.a:timers (.data .data.*) D + # libfreertos.a:timers (.rodata ...) D.2 # # rtc_bss # *(rtc.bss .rtc.bss) - # libfreertos.a:timers (.bss .bss.*) D - # libfreertos.a:timers (COMMON) D - mapping = u""" + # libfreertos.a:timers (.bss .bss.*) D + # libfreertos.a:timers (COMMON) D + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1028,7 +1047,9 @@ entries: # Commands for #4 F # Processed first due to alphabetical ordering - rtc_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + rtc_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) # Commands for #2 D # List all relevant sections excluding #3 for text -> rtc_text and D.2 @@ -1050,7 +1071,11 @@ entries: rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[1].sections, [])) # Commands for #3 E - iram0_text.append(InputSectionDesc(TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), [])) + iram0_text.append( + InputSectionDesc( + TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), [] + ) + ) dram0_data.append(InputSectionDesc(TIMERS, set(['.rodata.prvProcessReceivedCommands']), [])) self.compare_rules(expected, actual) @@ -1067,7 +1092,7 @@ entries: # * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...) # # iram0_text - mapping = u""" + mapping = """ [mapping:test_1] archive: libfreertos.a entries: @@ -1112,7 +1137,7 @@ entries: # # Uses the same entries as C_05 but spreads them across # two fragments. The output should still be the same. - mapping = u""" + mapping = """ [mapping:test_1] archive: libfreertos.a entries: @@ -1130,7 +1155,7 @@ entries: def test_mapping_same_lib_in_multiple_fragments_conflict(self): # Test mapping fragments operating on the same archive # with conflicting mappings. - mapping = u""" + mapping = """ [mapping:test_1] archive: libfreertos.a entries: @@ -1162,7 +1187,7 @@ entries: # libfreertos:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) G # libfreertos2:croutine(.text .literal ...) D # libfreertos2:croutine2(.text .literal ...) E - mapping = u""" + mapping = """ [mapping:freertos2] archive: libfreertos2.a entries: @@ -1200,8 +1225,12 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Command for - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), [])) - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []) + ) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine'), flash_text[0].sections, [])) iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine2'), flash_text[0].sections, [])) @@ -1210,7 +1239,7 @@ entries: def test_ambigious_obj(self): # Command generation for ambiguous entry should fail. - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1227,7 +1256,7 @@ entries: # # 'custom_scheme' entries conflict the 'default' scheme # entries. - mapping = u""" + mapping = """ [scheme:custom_scheme] entries: flash_text -> iram0_text @@ -1247,7 +1276,7 @@ entries: # # custom_scheme has the 'iram -> iram0_text' in common with # default scheme - mapping = u""" + mapping = """ [sections:custom_section] entries: .custom_section @@ -1282,7 +1311,7 @@ class ConfigTest(GenerationTest): def _test_conditional_on_scheme(self, perf, alt=None): # Test that proper commands are generated if using # schemes with conditional entries. - scheme = u""" + scheme = """ [sections:cond_text_data] entries: if PERFORMANCE_LEVEL >= 1: @@ -1299,7 +1328,7 @@ entries: cond_text_data -> dram0_data """ - mapping = u""" + mapping = """ [mapping:test] archive: lib.a entries: @@ -1334,7 +1363,7 @@ entries: def test_conditional_mapping(self, alt=None): # Test that proper commands are generated # in conditional mapping entries. - mapping = u""" + mapping = """ [mapping:default] archive: * entries: @@ -1383,7 +1412,7 @@ entries: def test_multiple_fragment_same_lib_conditional(self): # Test conditional entries on new mapping fragment grammar. # across multiple fragments. - mapping = u""" + mapping = """ [mapping:default] archive: * entries: @@ -1415,7 +1444,6 @@ entries: class FlagTest(GenerationTest): - # Test correct generation of mapping fragment entries # with flags. @@ -1446,7 +1474,7 @@ class FlagTest(GenerationTest): # libfreertos.a:croutine(.text .literal ...) I # . = ALIGN(4) G.2 # _sym1_end H.2 - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1506,7 +1534,7 @@ entries: # iram0_text # *(.iram .iram.*) # libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D - mapping = u""" + mapping = """ [mapping:default] archive: * entries: @@ -1550,7 +1578,9 @@ entries: flash_text.append(SymbolAtAddress('_sym1_end')) # Command for #3 D - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -1568,7 +1598,7 @@ entries: # iram0_text # *(.iram .iram.*) # libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1607,7 +1637,9 @@ entries: flash_text.append(SymbolAtAddress('_sym1_end')) # Command for #3 C - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -1624,7 +1656,7 @@ entries: # iram0_text # *(.iram .iram.*) # libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) C - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1659,7 +1691,9 @@ entries: flash_text.append(SymbolAtAddress('_sym1_end')) # Command for #3 C - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -1675,7 +1709,7 @@ entries: # iram0_text # *(.iram .iram.*) # libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D - mapping = u""" + mapping = """ [mapping:default] archive: * entries: @@ -1720,7 +1754,9 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Command for #4 D - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) @@ -1737,7 +1773,7 @@ entries: # iram0_text # *(.iram .iram.*) # libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D - mapping = u""" + mapping = """ [mapping:test] archive: libfreertos.a entries: @@ -1777,14 +1813,16 @@ entries: flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [])) # Command for #3 C - iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), [])) + iram0_text.append( + InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []) + ) self.compare_rules(expected, actual) def test_flag_additions(self): # Test ability to add flags as long as no other mapping fragments # does the same thing. - mapping = u""" + mapping = """ [mapping:default_add_flag] archive: * entries: @@ -1805,7 +1843,7 @@ entries: def test_flags_flag_additions_duplicate(self): # Test same flags added to same entity - these # are ignored. - mapping = u""" + mapping = """ [mapping:default_add_flag_1] archive: * entries: @@ -1832,7 +1870,7 @@ entries: def test_flags_flag_additions_conflict(self): # Test condition where multiple fragments specifies flags # to same entity - should generate exception. - mapping = u""" + mapping = """ [mapping:default_add_flag_1] archive: * entries: From 8ac9e65f4f13cd33625c720652513006da664e67 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Mon, 2 Jun 2025 16:21:17 +0200 Subject: [PATCH 5/9] feat(ldgen): print entity tree in debug mode Add the `--debug` option to print the entity tree to stdout. This can be useful for viewing the entire entity tree state to identify potential issues or incorrect placements. Signed-off-by: Frantisek Hrbata --- tools/ldgen/ldgen.py | 6 ++++++ tools/ldgen/ldgen/generation.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index e13ad5deb7..2dc6b41d7c 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -105,6 +105,12 @@ def main(): '--objdump', help='Path to toolchain objdump') + argparser.add_argument( + '--debug', '-d', + help='Print debugging information.', + action='store_true' + ) + args = argparser.parse_args() input_file = args.input diff --git a/tools/ldgen/ldgen/generation.py b/tools/ldgen/ldgen/generation.py index 59a1491ec1..32f281a5b7 100644 --- a/tools/ldgen/ldgen/generation.py +++ b/tools/ldgen/ldgen/generation.py @@ -122,6 +122,14 @@ class Placement: def add_subplacement(self, subplacement): self.subplacements.add(subplacement) + def __str__(self): + sections_str = ', '.join(self.sections) + basis_str = self.basis.node.name if self.basis else 'NONE' + return (f'({sections_str} -> {self.target}), basis: {basis_str}, ' + f'force: {self.force}, stable: {self.stable}, ' + f'explicit: {self.explicit}, flags: {self.flags}') + + class EntityNode: """ Node in entity tree. An EntityNode @@ -399,6 +407,7 @@ class Generation: self.schemes = {} self.placements = {} self.mappings = {} + self.debug = debug self.check_mappings = check_mappings @@ -587,6 +596,14 @@ class Generation: res.sort(key=lambda m: m.entity) return res + def _dump_entity_tree(self, node, indent=''): + print(f'{indent}-> {node.name}') + for placement in node.placements.values(): + print(f'{indent} {placement}') + print() + for child in node.children: + self._dump_entity_tree(child, ' ' * 8 + indent) + def generate(self, entities, non_contiguous_sram): scheme_dictionary = self._prepare_scheme_dictionary() entity_mappings = self._prepare_entity_mappings(scheme_dictionary, entities) @@ -598,6 +615,9 @@ class Generation: except ValueError as e: raise GenerationException(str(e)) + if self.debug: + self._dump_entity_tree(root_node) + # Traverse the tree, creating the placements commands = root_node.get_output_commands(non_contiguous_sram) From 1eceeaace2508e31935427148426a4be673baea2 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Thu, 22 May 2025 14:52:27 +0200 Subject: [PATCH 6/9] feat(build): provide ldgen with a list of mutable libraries The build system keeps track of each component source. Currently there are four types of sources: 1. "project_components" - project components 2. "project_extra_components" - components from EXTRA_COMPONENT_DIRS 3. "project_managed_components" - custom project dependencies managed by the IDF Component Manager 4. "idf_components" - ESP-IDF built-in components, typically under /components This can be used to identify the component libraries that are likely to change during application development and pass them to ldgen as mutable libraries. Add all components with "project_components" as their source as mutable. Signed-off-by: Frantisek Hrbata --- tools/cmake/ldgen.cmake | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/cmake/ldgen.cmake b/tools/cmake/ldgen.cmake index 2a354b7340..0b0b5b6a92 100644 --- a/tools/cmake/ldgen.cmake +++ b/tools/cmake/ldgen.cmake @@ -88,6 +88,32 @@ function(__ldgen_get_lib_deps_of_target target out_list_var) set(${out_list_var} ${out_list} PARENT_SCOPE) endfunction() +# __ldgen_get_mutable_libs +# +# Helper function to get the list of library file name generator expressions +# for project components. These libraries are placed by ldgen into a separate +# location in the linker script, allowing the fast reflashing feature. +# +function(__ldgen_get_mutable_libs out_list_var) + set(mutable_libs) + idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS) + foreach(component_target ${build_component_targets}) + __component_get_property(component_source ${component_target} COMPONENT_SOURCE) + __component_get_property(component_lib ${component_target} COMPONENT_LIB) + __component_get_property(component_type ${component_target} COMPONENT_TYPE) + if("${component_type}" STREQUAL "CONFIG_ONLY") + # Configuration only component interface target without a library. + continue() + endif() + if(NOT "${component_source}" STREQUAL "project_components") + # Add only project components as mutable. + continue() + endif() + set(unstable_lib "$") + list(APPEND mutable_libs "${unstable_lib}") + endforeach() + set(${out_list_var} ${mutable_libs} PARENT_SCOPE) +endfunction() # __ldgen_create_target # @@ -177,6 +203,17 @@ function(__ldgen_create_target exe_target) message(STATUS "Mapping check enabled in ldgen") endif() + if(CONFIG_ESPTOOLPY_FAST_REFLASHING) + # Create a file containing a list of mutable libraries used by ldgen + # for fast reflashing. + set(mutable_libs_path "${build_dir}/ldgen_mutable_libraries") + __ldgen_get_mutable_libs(mutable_libs) + list(JOIN mutable_libs "\n" mutable_libs_str) + file(GENERATE OUTPUT "${mutable_libs_path}" + CONTENT "${mutable_libs_str}") + set(mutable_libs_option "--mutable-libraries-file" "${mutable_libs_path}") + endif() + add_custom_command( OUTPUT ${output} COMMAND ${python} "${idf_path}/tools/ldgen/ldgen.py" @@ -189,6 +226,7 @@ function(__ldgen_create_target exe_target) --libraries-file "${build_dir}/ldgen_libraries" --objdump "${CMAKE_OBJDUMP}" ${ldgen_check} + ${mutable_libs_option} DEPENDS ${template} ${ldgen_fragment_files} ${ldgen_deps} ${SDKCONFIG} VERBATIM ) From a034ef871370638a3118a381bbef2327a590c7d5 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Tue, 3 Jun 2025 08:48:20 +0200 Subject: [PATCH 7/9] feat(build): add initial configuration for fast reflashing Introduce ESPTOOLPY_FAST_REFLASHING configuration option. It instructs ldgen to group entity mappings for libraries deemed mutable (prone to change) separately from those considered immutable (unlikely to change). Organizing mutable and immutable libraries separately in the linker script allows the linker to form larger contiguous blocks of data for immutable libraries in the application's output sections. These blocks are likely to stay mostly unchanged between application recompilations, enabling them to be skipped during reflashing. Separating mutable and immutable libraries in the linker script to minimize changes in the output sections is insufficient. Padding is added after the input sections of mutable libraries in the default data and text output sections. This creates a buffer for the mutable libraries, allowing additional changes to be made without altering the layout of the binary image. Additionally two optimizations currently in use can still mix data from these libraries, leading to significant changes even within the grouped immutable libraries. 1. constant merging Linker will try to merge input sections that have the MERGE and STRING flags from different libraries (object files) to perform optimizations like tail merging. For example, adding a string literal in a mutable library will also change the addresses of string literals from immutable libraries in such a merged section, causing changes in the generated code when those literals are referenced. Disabled with COMPILER_DISABLE_MERGE_CONSTANTS(-fno-merge-constants) 2. literal pools on Xtensa As optimization, the linker may merge literal pools from different libraries (object files) to improve the generated code size. This has the same effect as constant merging, and changes in mutable libraries may cause changes in the generated code for immutable libraries. To get larger unchanged continuous blocks in the text output sections for immutable libraries, we need to ensure that the Xtensa literal pools remain close to their references and are not merged. Disabled with CONFIG_COMPILER_ENABLE_TEXT_SECTION_LITERALS(-mtext-section-literals) When ESPTOOLPY_FAST_REFLASHING is enabled, these two optimizations are disabled to achieve larger unchaged continuous blocks for the grouped immutable libraries, even though disabling these optimizations results in slightly larger code. Signed-off-by: Frantisek Hrbata --- CMakeLists.txt | 4 ++ Kconfig | 15 ++++++ components/esptool_py/Kconfig.projbuild | 61 +++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e00a17e2e9..c6c4fb23ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,10 @@ if(CONFIG_COMPILER_NO_MERGE_CONSTANTS) list(APPEND compile_options "-fno-merge-constants") endif() +if(CONFIG_COMPILER_ENABLE_TEXT_SECTION_LITERALS) + list(APPEND compile_options "-mtext-section-literals") +endif() + if(CONFIG_COMPILER_STACK_CHECK_MODE_NORM) list(APPEND compile_options "-fstack-protector") elseif(CONFIG_COMPILER_STACK_CHECK_MODE_STRONG) diff --git a/Kconfig b/Kconfig index fec451c131..59957e2761 100644 --- a/Kconfig +++ b/Kconfig @@ -547,6 +547,20 @@ mainmenu "Espressif IoT Development Framework Configuration" distribution is more uniform across libraries. On downside, it may increase the binary size and hence should be used during development phase only. + config COMPILER_ENABLE_TEXT_SECTION_LITERALS + bool + depends on IDF_TOOLCHAIN_GCC + depends on IDF_TARGET_ARCH_XTENSA + default y if ESPTOOLPY_FAST_REFLASHING + help + Intersperse Xtensa literals within the text section to keep + them as close as possible to their references. This prevents + literals from being placed into a separate section in the + output file and prevents the linker from combining literal + pools from different object files. Enabling this is necessary + for fast reflashing to prevent mixing code from mutable and + immutable libraries. + config COMPILER_WARN_WRITE_STRINGS bool "Enable -Wwrite-strings warning flag" default "n" @@ -755,3 +769,4 @@ mainmenu "Espressif IoT Development Framework Configuration" - CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION - CONFIG_I3C_MASTER_ENABLED - CONFIG_MBEDTLS_ESP_IDF_USE_PSA_CRYPTO + - CONFIG_ESPTOOLPY_FAST_REFLASHING diff --git a/components/esptool_py/Kconfig.projbuild b/components/esptool_py/Kconfig.projbuild index bbaa954ff1..8e6c99d908 100644 --- a/components/esptool_py/Kconfig.projbuild +++ b/components/esptool_py/Kconfig.projbuild @@ -202,4 +202,65 @@ menu "Serial flasher config" int default ESP_CONSOLE_UART_BAUDRATE if ESP_CONSOLE_UART default 115200 if !ESP_CONSOLE_UART + + menu "Fast Reflashing" + + config ESPTOOLPY_FAST_REFLASHING + bool "Enable fast reflashing (Experimental)" + depends on IDF_EXPERIMENTAL_FEATURES + select COMPILER_NO_MERGE_CONSTANTS if IDF_TOOLCHAIN_GCC + help + Enabling this option classifies component libraries into two + categories: mutable and immutable. Mutable libraries are + expected to change frequently during development, while + immutable libraries are considered stable. All project + component libraries are treated as mutable; all other libraries + are treated as immutable. + + In the generated linker script, input sections from immutable + libraries are placed before those from mutable libraries. This + layout helps localize changes in the output sections of the ELF + file, so that recompilation primarily affects a confined area + associated with the mutable libraries. + + This enables the generation of binary images with large + unmodified regions across recompilations, allowing to flash + only the parts that have changed. + + To support this, some compiler optimizations, such as constant + merging, are disabled, which helps minimize differences in the + binary image between builds. However, this may increase the + overall image size and flash usage. + + Additionally, this option inserts a padding, defined by the + ESPTOOLPY_FAST_REFLASHING_PADDING option, after the input + sections of mutable libraries. This allows further isolate + changes but also increases the size of the binary image and + flash consumption. + + choice ESPTOOLPY_FAST_REFLASHING_PADDING_SIZE + prompt "Padding size" + default ESPTOOLPY_FAST_REFLASHING_PADDING_4KB + depends on ESPTOOLPY_FAST_REFLASHING + help + Selects the amount of padding (in kilobytes) to add after input + sections of mutable libraries when fast reflashing is enabled. + + config ESPTOOLPY_FAST_REFLASHING_PADDING_2KB + bool "2 KB" + config ESPTOOLPY_FAST_REFLASHING_PADDING_4KB + bool "4 KB" + config ESPTOOLPY_FAST_REFLASHING_PADDING_8KB + bool "8 KB" + endchoice + + config ESPTOOLPY_FAST_REFLASHING_PADDING + int + depends on ESPTOOLPY_FAST_REFLASHING + default 2048 if ESPTOOLPY_FAST_REFLASHING_PADDING_2KB + default 4096 if ESPTOOLPY_FAST_REFLASHING_PADDING_4KB + default 8192 if ESPTOOLPY_FAST_REFLASHING_PADDING_8KB + + endmenu + endmenu From e65807a9011d23c49d05a6a2e850e95155e1c434 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Mon, 9 Jun 2025 11:55:24 +0200 Subject: [PATCH 8/9] fix(ldgen): ruff formating changes and copyright update Changes generated by pre-commit related to ruff-format and an update to the copyright date. Signed-off-by: Frantisek Hrbata --- tools/ldgen/ldgen.py | 91 +++++++++------------- tools/ldgen/ldgen/generation.py | 111 +++++++++++++++------------ tools/ldgen/ldgen/output_commands.py | 33 ++++---- 3 files changed, 116 insertions(+), 119 deletions(-) diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index 2dc6b41d7c..20b94a6f01 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # import argparse @@ -23,7 +23,7 @@ from pyparsing import ParseFatalException def _update_environment(args): - env = [(name, value) for (name,value) in (e.split('=',1) for e in args.env)] + env = [(name, value) for (name, value) in (e.split('=', 1) for e in args.env)] for name, value in env: value = ' '.join(value.split()) os.environ[name] = value @@ -34,83 +34,64 @@ def _update_environment(args): def main(): - argparser = argparse.ArgumentParser(description='ESP-IDF linker script generator') - argparser.add_argument( - '--input', '-i', - help='Linker template file', - type=argparse.FileType('r')) + argparser.add_argument('--input', '-i', help='Linker template file', type=argparse.FileType('r')) fragments_group = argparser.add_mutually_exclusive_group() fragments_group.add_argument( - '--fragments', '-f', - type=argparse.FileType('r'), - help='Input fragment files', - nargs='+' + '--fragments', '-f', type=argparse.FileType('r'), help='Input fragment files', nargs='+' ) fragments_group.add_argument( - '--fragments-list', - help='Input fragment files as a semicolon-separated list', - type=str + '--fragments-list', help='Input fragment files as a semicolon-separated list', type=str ) argparser.add_argument( - '--libraries-file', - type=argparse.FileType('r'), - help='File that contains the list of libraries in the build') + '--libraries-file', type=argparse.FileType('r'), help='File that contains the list of libraries in the build' + ) argparser.add_argument( '--mutable-libraries-file', type=argparse.FileType('r'), - help='File that contains the list of mutable libraries in the build') + help='File that contains the list of mutable libraries in the build', + ) + + argparser.add_argument('--output', '-o', help='Output linker script', type=str) + + argparser.add_argument('--config', '-c', help='Project configuration') + + argparser.add_argument('--kconfig', '-k', help='IDF Kconfig file') argparser.add_argument( - '--output', '-o', - help='Output linker script', - type=str) - - argparser.add_argument( - '--config', '-c', - help='Project configuration') - - argparser.add_argument( - '--kconfig', '-k', - help='IDF Kconfig file') - - argparser.add_argument( - '--check-mapping', - help='Perform a check if a mapping (archive, obj, symbol) exists', - action='store_true' + '--check-mapping', help='Perform a check if a mapping (archive, obj, symbol) exists', action='store_true' ) argparser.add_argument( - '--check-mapping-exceptions', - help='Mappings exempted from check', - type=argparse.FileType('r') + '--check-mapping-exceptions', help='Mappings exempted from check', type=argparse.FileType('r') ) argparser.add_argument( - '--env', '-e', - action='append', default=[], - help='Environment to set when evaluating the config file', metavar='NAME=VAL') - - argparser.add_argument('--env-file', type=argparse.FileType('r'), - help='Optional file to load environment variables from. Contents ' - 'should be a JSON object where each key/value pair is a variable.') - - argparser.add_argument( - '--objdump', - help='Path to toolchain objdump') - - argparser.add_argument( - '--debug', '-d', - help='Print debugging information.', - action='store_true' + '--env', + '-e', + action='append', + default=[], + help='Environment to set when evaluating the config file', + metavar='NAME=VAL', ) + argparser.add_argument( + '--env-file', + type=argparse.FileType('r'), + help='Optional file to load environment variables from. Contents ' + 'should be a JSON object where each key/value pair is a variable.', + ) + + argparser.add_argument('--objdump', help='Path to toolchain objdump') + + argparser.add_argument('--debug', '-d', help='Print debugging information.', action='store_true') + args = argparser.parse_args() input_file = args.input @@ -178,7 +159,9 @@ def main(): if exc.errno != errno.EEXIST: raise - with open(output_path, 'w', encoding='utf-8') as f: # only create output file after generation has succeeded + with open( + output_path, 'w', encoding='utf-8' + ) as f: # only create output file after generation has succeeded f.write(output.read()) except LdGenFailure as e: print('linker script generation failed for %s\nERROR: %s' % (input_file.name, e)) diff --git a/tools/ldgen/ldgen/generation.py b/tools/ldgen/ldgen/generation.py index 32f281a5b7..1ea4af52ce 100644 --- a/tools/ldgen/ldgen/generation.py +++ b/tools/ldgen/ldgen/generation.py @@ -92,12 +92,14 @@ class Placement: # designated area (MutableMarker) in the linker script. If the basis is # also mutable, the placement is deemed insignificant to prevent adding # unnecessary EXCLUDE_FILE and placement rules to the linker script. - significant = (not self.basis or - self.target != self.basis.target or - (self.flags and not self.basis.flags) or - (not self.flags and self.basis.flags) or - (self.mutable and not self.basis.mutable) or - self.force) + significant = ( + not self.basis + or self.target != self.basis.target + or (self.flags and not self.basis.flags) + or (not self.flags and self.basis.flags) + or (self.mutable and not self.basis.mutable) + or self.force + ) if significant and not self.explicit and not self.sections: # The placement is significant, but it is an intermediate placement @@ -125,9 +127,11 @@ class Placement: def __str__(self): sections_str = ', '.join(self.sections) basis_str = self.basis.node.name if self.basis else 'NONE' - return (f'({sections_str} -> {self.target}), basis: {basis_str}, ' - f'force: {self.force}, stable: {self.stable}, ' - f'explicit: {self.explicit}, flags: {self.flags}') + return ( + f'({sections_str} -> {self.target}), basis: {basis_str}, ' + f'force: {self.force}, stable: {self.stable}, ' + f'explicit: {self.explicit}, flags: {self.flags}' + ) class EntityNode: @@ -172,7 +176,7 @@ class EntityNode: assert name and name != Entity.ALL child = [c for c in self.children if c.name == name] - assert (len(child) <= 1) + assert len(child) <= 1 if not child: child = self.child_t(self, name) @@ -186,7 +190,7 @@ class EntityNode: commands = collections.defaultdict(list) def process_commands(cmds): - for (target, commands_list) in cmds.items(): + for target, commands_list in cmds.items(): commands[target].extend(commands_list) # Process the commands generated from this node @@ -230,7 +234,9 @@ class EntityNode: for flag in surround_type: if flag.pre: if isinstance(flag, Surround): - commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_start', tied, mutable=mutable)) + commands[placement.target].append( + SymbolAtAddress(f'_{flag.symbol}_start', tied, mutable=mutable) + ) else: # ALIGN commands[placement.target].append(AlignAtAddress(flag.alignment, tied, mutable=mutable)) @@ -239,22 +245,38 @@ class EntityNode: placement_sections = frozenset(placement.sections) command_sections = sections if sections == placement_sections else placement_sections - command = InputSectionDesc(placement.node.entity, command_sections, - [e.node.entity for e in placement.exclusions], keep, sort, tied, mutable=mutable) + command = InputSectionDesc( + placement.node.entity, + command_sections, + [e.node.entity for e in placement.exclusions], + keep, + sort, + tied, + mutable=mutable, + ) commands[placement.target].append(command) # Generate commands for intermediate, non-explicit exclusion placements here, # so that they can be enclosed by flags that affect the parent placement. for subplacement in placement.subplacements: if not subplacement.flags and not subplacement.explicit: - command = InputSectionDesc(subplacement.node.entity, subplacement.sections, - [e.node.entity for e in subplacement.exclusions], keep, sort, tied, mutable=mutable) + command = InputSectionDesc( + subplacement.node.entity, + subplacement.sections, + [e.node.entity for e in subplacement.exclusions], + keep, + sort, + tied, + mutable=mutable, + ) commands[placement.target].append(command) for flag in surround_type: if flag.post: if isinstance(flag, Surround): - commands[placement.target].append(SymbolAtAddress(f'_{flag.symbol}_end', tied, mutable=mutable)) + commands[placement.target].append( + SymbolAtAddress(f'_{flag.symbol}_end', tied, mutable=mutable) + ) else: # ALIGN commands[placement.target].append(AlignAtAddress(flag.alignment, tied, mutable=mutable)) @@ -349,7 +371,9 @@ class ObjectNode(EntityNode): obj_placement = self.placements[sections] except KeyError: # Create intermediate placement. - obj_placement = self.self_placement(sections, sym_placement.basis.target, None, False, mutable=mutable) + obj_placement = self.self_placement( + sections, sym_placement.basis.target, None, False, mutable=mutable + ) if obj_placement.basis.flags: subplace = True @@ -428,7 +452,7 @@ class Generation: for scheme in self.schemes.values(): sections_bucket = collections.defaultdict(list) - for (sections_name, target_name) in scheme.entries: + for sections_name, target_name in scheme.entries: # Get the sections under the bucket 'target_name'. If this bucket does not exist # is created automatically sections_in_bucket = sections_bucket[target_name] @@ -444,7 +468,7 @@ class Generation: scheme_dictionary[scheme.name] = sections_bucket # Search for and raise exception on first instance of sections mapped to multiple targets - for (scheme_name, sections_bucket) in scheme_dictionary.items(): + for scheme_name, sections_bucket in scheme_dictionary.items(): for sections_a, sections_b in itertools.combinations(sections_bucket.values(), 2): set_a = set() set_b = set() @@ -490,11 +514,7 @@ class Generation: # Mutable library already has entity mapping. continue - entity_mappings[key] = Generation.EntityMapping(entity, - sections_str, - target, - [], - True) + entity_mappings[key] = Generation.EntityMapping(entity, sections_str, target, [], True) for key, mapping in entity_mappings.items(): (entity, sections, target, flags, mutable) = mapping @@ -503,11 +523,7 @@ class Generation: # an archive entity that has already been marked as mutable. continue # Set entity as mutable. - entity_mappings[key] = Generation.EntityMapping(entity, - sections, - target, - flags, - True) + entity_mappings[key] = Generation.EntityMapping(entity, sections, target, flags, True) def _prepare_entity_mappings(self, scheme_dictionary, entities): # Prepare entity mappings processed from mapping fragment entries. @@ -516,13 +532,15 @@ class Generation: for mapping in self.mappings.values(): archive = mapping.archive - for (obj, symbol, scheme_name) in mapping.entries: + for obj, symbol, scheme_name in mapping.entries: entity = Entity(archive, obj, symbol) # Check the entity exists - if (self.check_mappings - and entity.specificity.value > Entity.Specificity.ARCHIVE.value - and mapping.name not in self.check_mapping_exceptions): + if ( + self.check_mappings + and entity.specificity.value > Entity.Specificity.ARCHIVE.value + and mapping.name not in self.check_mapping_exceptions + ): if not entities.check_exists(entity): message = "'%s' not found" % str(entity) raise GenerationException(message, mapping) @@ -532,16 +550,16 @@ class Generation: # Check if all section->target defined in the current # scheme. for flag in flags: - if (flag.target not in scheme_dictionary[scheme_name].keys() - or flag.section not in - [_s.name for _s in scheme_dictionary[scheme_name][flag.target]]): + if flag.target not in scheme_dictionary[scheme_name].keys() or flag.section not in [ + _s.name for _s in scheme_dictionary[scheme_name][flag.target] + ]: message = "%s->%s not defined in scheme '%s'" % (flag.section, flag.target, scheme_name) raise GenerationException(message, mapping) else: flags = None # Create placement for each 'section -> target' in the scheme. - for (target, sections) in scheme_dictionary[scheme_name].items(): + for target, sections in scheme_dictionary[scheme_name].items(): for section in sections: # Find the applicable flags _flags = [] @@ -561,11 +579,7 @@ class Generation: existing = None if not existing: - entity_mappings[key] = Generation.EntityMapping(entity, - sections_str, - target, - _flags, - False) + entity_mappings[key] = Generation.EntityMapping(entity, sections_str, target, _flags, False) else: # Check for conflicts. if target != existing.target: @@ -576,11 +590,9 @@ class Generation: if _flags or existing.flags: if (_flags and not existing.flags) or (not _flags and existing.flags): _flags.extend(existing.flags) - entity_mappings[key] = Generation.EntityMapping(entity, - sections_str, - target, - _flags, - False) + entity_mappings[key] = Generation.EntityMapping( + entity, sections_str, target, _flags, False + ) elif _flags == existing.flags: pass else: @@ -636,8 +648,7 @@ class Generation: if fragment.name in dict_to_append_to: stored = dict_to_append_to[fragment.name].path new = fragment.path - message = "Duplicate definition of fragment '%s' found in %s and %s." % ( - fragment.name, stored, new) + message = "Duplicate definition of fragment '%s' found in %s and %s." % (fragment.name, stored, new) raise GenerationException(message) dict_to_append_to[fragment.name] = fragment diff --git a/tools/ldgen/ldgen/output_commands.py b/tools/ldgen/ldgen/output_commands.py index c3d2ff0af6..853f2d0f4b 100644 --- a/tools/ldgen/ldgen/output_commands.py +++ b/tools/ldgen/ldgen/output_commands.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # from .entity import Entity @@ -25,11 +25,10 @@ class AlignAtAddress: self.mutable = mutable def __str__(self): - return ('. = ALIGN(%d);' % self.alignment) + return '. = ALIGN(%d);' % self.alignment def __eq__(self, other): - return (isinstance(other, AlignAtAddress) and - self.alignment == other.alignment) + return isinstance(other, AlignAtAddress) and self.alignment == other.alignment class SymbolAtAddress: @@ -50,11 +49,10 @@ class SymbolAtAddress: self.mutable = mutable def __str__(self): - return ('%s = ABSOLUTE(.);' % self.symbol) + return '%s = ABSOLUTE(.);' % self.symbol def __eq__(self, other): - return (isinstance(other, SymbolAtAddress) and - self.symbol == other.symbol) + return isinstance(other, SymbolAtAddress) and self.symbol == other.symbol class InputSectionDesc: @@ -76,8 +74,11 @@ class InputSectionDesc: self.exclusions = set() if exclusions: - assert not [e for e in exclusions if e.specificity == Entity.Specificity.SYMBOL or - e.specificity == Entity.Specificity.NONE] + assert not [ + e + for e in exclusions + if e.specificity == Entity.Specificity.SYMBOL or e.specificity == Entity.Specificity.NONE + ] self.exclusions = set(exclusions) else: self.exclusions = set() @@ -152,9 +153,11 @@ class InputSectionDesc: return res def __eq__(self, other): - return (isinstance(other, InputSectionDesc) and - self.entity == other.entity and - self.sections == other.sections and - self.exclusions == other.exclusions and - self.keep == other.keep and - self.sort == other.sort) + return ( + isinstance(other, InputSectionDesc) + and self.entity == other.entity + and self.sections == other.sections + and self.exclusions == other.exclusions + and self.keep == other.keep + and self.sort == other.sort + ) From 368e55a7bcc62ef417ad126485e9627a0d7303f6 Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Wed, 22 Oct 2025 14:05:54 +0200 Subject: [PATCH 9/9] fix(ldgen): use format specifiers instead of percent format Resolve ruff's UP031 errors related to the use of percent formatting for strings. Signed-off-by: Frantisek Hrbata --- tools/ldgen/ldgen.py | 4 +-- tools/ldgen/ldgen/generation.py | 10 +++---- tools/ldgen/ldgen/linker_script.py | 2 +- tools/ldgen/ldgen/output_commands.py | 40 ++++++++++++++-------------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tools/ldgen/ldgen.py b/tools/ldgen/ldgen.py index 20b94a6f01..b07bfd0967 100755 --- a/tools/ldgen/ldgen.py +++ b/tools/ldgen/ldgen.py @@ -139,7 +139,7 @@ def main(): # ParseException is raised on incorrect grammar # ParseFatalException is raised on correct grammar, but inconsistent contents (ex. duplicate # keys, key unsupported by fragment, unexpected number of values, etc.) - raise LdGenFailure('failed to parse %s\n%s' % (fragment_file, str(e))) + raise LdGenFailure(f'failed to parse {fragment_file}\n{e}') generation_model.add_fragments_from_file(fragment_file) non_contiguous_sram = sdkconfig.evaluate_expression('SOC_MEM_NON_CONTIGUOUS_SRAM') @@ -164,7 +164,7 @@ def main(): ) as f: # only create output file after generation has succeeded f.write(output.read()) except LdGenFailure as e: - print('linker script generation failed for %s\nERROR: %s' % (input_file.name, e)) + print(f'linker script generation failed for {input_file.name}\nERROR: {e}') sys.exit(1) diff --git a/tools/ldgen/ldgen/generation.py b/tools/ldgen/ldgen/generation.py index 1ea4af52ce..6d6a5ad5da 100644 --- a/tools/ldgen/ldgen/generation.py +++ b/tools/ldgen/ldgen/generation.py @@ -359,7 +359,7 @@ class ObjectNode(EntityNode): if obj_sections: symbol = entity.symbol - remove_sections = [s.replace('.*', '.%s' % symbol) for s in sections if '.*' in s] + remove_sections = [s.replace('.*', f'.{symbol}') for s in sections if '.*' in s] filtered_sections = [s for s in obj_sections if s not in remove_sections] if set(filtered_sections) != set(obj_sections): @@ -542,7 +542,7 @@ class Generation: and mapping.name not in self.check_mapping_exceptions ): if not entities.check_exists(entity): - message = "'%s' not found" % str(entity) + message = f"'{entity}' not found" raise GenerationException(message, mapping) if (obj, symbol, scheme_name) in mapping.flags.keys(): @@ -553,7 +553,7 @@ class Generation: if flag.target not in scheme_dictionary[scheme_name].keys() or flag.section not in [ _s.name for _s in scheme_dictionary[scheme_name][flag.target] ]: - message = "%s->%s not defined in scheme '%s'" % (flag.section, flag.target, scheme_name) + message = f"{flag.section}->{flag.target} not defined in scheme '{scheme_name}'" raise GenerationException(message, mapping) else: flags = None @@ -648,7 +648,7 @@ class Generation: if fragment.name in dict_to_append_to: stored = dict_to_append_to[fragment.name].path new = fragment.path - message = "Duplicate definition of fragment '%s' found in %s and %s." % (fragment.name, stored, new) + message = f"Duplicate definition of fragment '{fragment.name}' found in {stored} and {new}." raise GenerationException(message) dict_to_append_to[fragment.name] = fragment @@ -668,6 +668,6 @@ class GenerationException(LdGenFailure): def __str__(self): if self.fragment: - return "%s\nIn fragment '%s' defined in '%s'." % (self.message, self.fragment.name, self.fragment.path) + return f"{self.message}\nIn fragment '{self.fragment.name}' defined in '{self.fragment.path}'." else: return self.message diff --git a/tools/ldgen/ldgen/linker_script.py b/tools/ldgen/ldgen/linker_script.py index c74e478c8c..571d925b66 100644 --- a/tools/ldgen/ldgen/linker_script.py +++ b/tools/ldgen/ldgen/linker_script.py @@ -84,7 +84,7 @@ class LinkerScript: # Add information that this is a generated file. output_file.write('/* Automatically generated file; DO NOT EDIT */\n') output_file.write('/* Espressif IoT Development Framework Linker Script */\n') - output_file.write('/* Generated from: %s */\n' % self.file) + output_file.write(f'/* Generated from: {self.file} */\n') output_file.write('\n') # Do the text replacement diff --git a/tools/ldgen/ldgen/output_commands.py b/tools/ldgen/ldgen/output_commands.py index 853f2d0f4b..a8332a9432 100644 --- a/tools/ldgen/ldgen/output_commands.py +++ b/tools/ldgen/ldgen/output_commands.py @@ -25,7 +25,7 @@ class AlignAtAddress: self.mutable = mutable def __str__(self): - return '. = ALIGN(%d);' % self.alignment + return f'. = ALIGN({self.alignment});' def __eq__(self, other): return isinstance(other, AlignAtAddress) and self.alignment == other.alignment @@ -49,7 +49,7 @@ class SymbolAtAddress: self.mutable = mutable def __str__(self): - return '%s = ABSOLUTE(.);' % self.symbol + return f'{self.symbol} = ABSOLUTE(.);' def __eq__(self, other): return isinstance(other, SymbolAtAddress) and self.symbol == other.symbol @@ -97,56 +97,56 @@ class InputSectionDesc: for exc in sorted(self.exclusions): if exc.specificity == Entity.Specificity.ARCHIVE: - exc_string = '*%s' % (exc.archive) + exc_string = f'*{exc.archive}' else: - exc_string = '*%s:%s.*' % (exc.archive, exc.obj) + exc_string = f'*{exc.archive}:{exc.obj}.*' exclusion_strings.append(exc_string) section_strings = [] if exclusion_strings: - exclusion_string = 'EXCLUDE_FILE(%s)' % ' '.join(exclusion_strings) + exclusion_string = 'EXCLUDE_FILE({})'.format(' '.join(exclusion_strings)) for section in sorted(self.sections): - section_strings.append('%s %s' % (exclusion_string, section)) + section_strings.append(f'{exclusion_string} {section}') else: for section in sorted(self.sections): section_strings.append(section) if self.sort: if self.sort == (None, None): - pattern = 'SORT(%s)' + pattern = 'SORT({})' elif self.sort == ('name', None): - pattern = 'SORT_BY_NAME(%s)' + pattern = 'SORT_BY_NAME({})' elif self.sort == ('alignment', None): - pattern = 'SORT_BY_ALIGNMENT(%s)' + pattern = 'SORT_BY_ALIGNMENT({})' elif self.sort == ('init_priority', None): - pattern = 'SORT_BY_INIT_PRIORITY(%s)' + pattern = 'SORT_BY_INIT_PRIORITY({})' elif self.sort == ('name', 'alignment'): - pattern = 'SORT_BY_NAME(SORT_BY_ALIGNMENT(%s))' + pattern = 'SORT_BY_NAME(SORT_BY_ALIGNMENT({}))' elif self.sort == ('alignment', 'name'): - pattern = 'SORT_BY_ALIGNMENT(SORT_BY_NAME(%s))' + pattern = 'SORT_BY_ALIGNMENT(SORT_BY_NAME({}))' elif self.sort == ('name', 'name'): - pattern = 'SORT_BY_NAME(SORT_BY_NAME(%s))' + pattern = 'SORT_BY_NAME(SORT_BY_NAME({}))' elif self.sort == ('alignment', 'alignment'): - pattern = 'SORT_BY_ALIGNMENT(SORT_BY_ALIGNMENT(%s))' + pattern = 'SORT_BY_ALIGNMENT(SORT_BY_ALIGNMENT({}))' else: raise Exception('Invalid sort arguments') - section_strings = [(pattern % s) for s in section_strings] + section_strings = [pattern.format(s) for s in section_strings] - sections_string = '(%s)' % ' '.join(section_strings) + sections_string = '({})'.format(' '.join(section_strings)) if self.entity.specificity == Entity.Specificity.NONE: - entry = '*%s' % (sections_string) + entry = f'*{sections_string}' elif self.entity.specificity == Entity.Specificity.ARCHIVE: - entry = '*%s:%s' % (self.entity.archive, sections_string) + entry = f'*{self.entity.archive}:{sections_string}' else: - entry = '*%s:%s.*%s' % (self.entity.archive, self.entity.obj, sections_string) + entry = f'*{self.entity.archive}:{self.entity.obj}.*{sections_string}' if self.keep: - res = 'KEEP(%s)' % entry + res = f'KEEP({entry})' else: res = entry