Merge branch 'feature/linker_script_generator' into 'master'

Linker script generator

See merge request idf/esp-idf!2286
This commit is contained in:
Angus Gratton 2018-11-19 12:43:27 +08:00
commit 8915f48208
62 changed files with 13981 additions and 67 deletions

View File

@ -441,6 +441,13 @@ test_fatfs_on_host:
- cd components/fatfs/test_fatfs_host/
- make test
test_ldgen_on_host:
<<: *host_test_template
script:
- cd tools/ldgen/test
- ./test_fragments.py
- ./test_generation.py
.host_fuzzer_test_template: &host_fuzzer_test_template
stage: host_test
image: $CI_DOCKER_REGISTRY/afl-fuzzer-test

View File

@ -18,6 +18,7 @@ endif()
set(COMPONENT_REQUIRES)
set(COMPONENT_PRIV_REQUIRES xtensa-debug-module)
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
register_component()

View File

@ -26,4 +26,6 @@ COMPONENT_SRCDIRS += \
sys_view/esp32
else
COMPONENT_SRCDIRS += gcov
COMPONENT_ADD_LDFRAGMENTS += linker.lf
endif

View File

@ -0,0 +1,5 @@
[mapping]
archive: libapp_trace.a
entries:
* (noflash)

View File

@ -71,6 +71,8 @@ else()
app_trace bootloader_support ethernet log mbedtls nvs_flash
pthread smartconfig_ack spi_flash vfs wpa_supplicant xtensa-debug-module)
set(COMPONENT_ADD_LDFRAGMENTS linker.lf ld/esp32_fragments.lf)
register_component()
target_link_libraries(esp32 "-L ${CMAKE_CURRENT_SOURCE_DIR}/lib")
@ -84,7 +86,16 @@ else()
target_linker_script(esp32 "ld/esp32.extram.bss.ld")
endif()
target_linker_script(esp32 "ld/esp32.common.ld")
# Process the template file through the linker script generation mechanism, and use the output for linking the
# final binary
set(esp32_common_script "${CMAKE_CURRENT_BINARY_DIR}/esp32.common.ld")
set(esp32_common_template "${CMAKE_CURRENT_LIST_DIR}/ld/esp32.common.ld.in")
ldgen_process_template(${esp32_common_template} ${esp32_common_script})
target_link_libraries(esp32 "-T ${esp32_common_script}")
set_property(TARGET ${PROJECT_NAME}.elf APPEND PROPERTY LINK_DEPENDS ${esp32_common_script})
target_linker_script(esp32
"ld/esp32.rom.ld"
@ -146,7 +157,7 @@ else()
endif()
# Enable dynamic esp_timer overflow value if building unit tests
# Enable dynamic esp_timer overflow value if building unit tests
if(NOT "${BUILD_TEST_COMPONENTS}" EQUAL "")
add_definitions(-DESP_TIMER_DYNAMIC_OVERFLOW_VAL)
endif()

View File

@ -42,3 +42,9 @@ endif
ifneq ("$(TEST_COMPONENTS_LIST)","")
CPPFLAGS += -DESP_TIMER_DYNAMIC_OVERFLOW_VAL
endif
ESP32_LINKER_SCRIPT_TEMPLATE := $(COMPONENT_PATH)/ld/esp32.common.ld.in
ESP32_LINKER_SCRIPT_OUTPUT_DIR := $(abspath $(BUILD_DIR_BASE)/esp32)
# Target to generate linker script generator from fragments presented by each of
# the components
$(eval $(call ldgen_process_template, $(ESP32_LINKER_SCRIPT_TEMPLATE), $(ESP32_LINKER_SCRIPT_OUTPUT_DIR)/esp32.common.ld))

View File

@ -16,7 +16,7 @@ endif
#Linker scripts used to link the final application.
#Warning: These linker scripts are only used when the normal app is compiled; the bootloader
#specifies its own scripts.
LINKER_SCRIPTS += esp32.common.ld esp32.rom.ld esp32.peripherals.ld
LINKER_SCRIPTS += $(COMPONENT_BUILD_DIR)/esp32.common.ld esp32.rom.ld esp32.peripherals.ld
#Force pure functions from libgcc.a to be linked from ROM
LINKER_SCRIPTS += esp32.rom.libgcc.ld
@ -35,7 +35,7 @@ ifndef CONFIG_SPI_FLASH_ROM_DRIVER_PATCH
LINKER_SCRIPTS += esp32.rom.spiflash.ld
endif
#ld_include_panic_highint_hdl is added as an undefined symbol because otherwise the
#ld_include_panic_highint_hdl is added as an undefined symbol because otherwise the
#linker will ignore panic_highint_hdl.S as it has no other files depending on any
#symbols in it.
COMPONENT_ADD_LDFLAGS += $(COMPONENT_PATH)/libhal.a \
@ -44,7 +44,9 @@ COMPONENT_ADD_LDFLAGS += $(COMPONENT_PATH)/libhal.a \
-L $(COMPONENT_PATH)/ld \
-T esp32_out.ld \
-u ld_include_panic_highint_hdl \
$(addprefix -T ,$(LINKER_SCRIPTS))
$(addprefix -T ,$(LINKER_SCRIPTS)) \
COMPONENT_ADD_LDFRAGMENTS += ld/esp32_fragments.lf linker.lf
ALL_LIB_FILES := $(patsubst %,$(COMPONENT_PATH)/lib/lib%.a,$(LIBS))
@ -52,7 +54,9 @@ COMPONENT_SUBMODULES += lib
# final linking of project ELF depends on all binary libraries, and
# all linker scripts (except esp32_out.ld, as this is code generated here.)
COMPONENT_ADD_LINKER_DEPS := $(ALL_LIB_FILES) $(addprefix ld/,$(LINKER_SCRIPTS))
COMPONENT_ADD_LINKER_DEPS := $(ALL_LIB_FILES) \
$(addprefix ld/, $(filter-out $(COMPONENT_BUILD_DIR)/esp32.common.ld, $(LINKER_SCRIPTS))) \
$(COMPONENT_BUILD_DIR)/esp32.common.ld
# Preprocess esp32.ld linker script into esp32_out.ld
#
@ -63,7 +67,7 @@ $(COMPONENT_LIBRARY): esp32_out.ld
esp32_out.ld: $(COMPONENT_PATH)/ld/esp32.ld ../include/sdkconfig.h
$(CC) -I ../include -C -P -x c -E $< -o $@
COMPONENT_EXTRA_CLEAN := esp32_out.ld
COMPONENT_EXTRA_CLEAN := esp32_out.ld $(COMPONENT_BUILD_DIR)/esp32.common.ld
# disable stack protection in files which are involved in initialization of that feature
stack_check.o: CFLAGS := $(filter-out -fstack-protector%, $(CFLAGS))

View File

@ -9,7 +9,9 @@ SECTIONS
.rtc.text :
{
. = ALIGN(4);
*(.rtc.literal .rtc.text)
mapping[rtc_text]
*rtc_wake_stub*.*(.literal .text .literal.* .text.*)
_rtc_text_end = ABSOLUTE(.);
} > rtc_iram_seg
@ -49,8 +51,9 @@ SECTIONS
.rtc.data :
{
_rtc_data_start = ABSOLUTE(.);
*(.rtc.data)
*(.rtc.rodata)
mapping[rtc_data]
*rtc_wake_stub*.*(.data .rodata .data.* .rodata.* .bss .bss.*)
_rtc_data_end = ABSOLUTE(.);
} > rtc_data_location
@ -61,7 +64,9 @@ SECTIONS
_rtc_bss_start = ABSOLUTE(.);
*rtc_wake_stub*.*(.bss .bss.*)
*rtc_wake_stub*.*(COMMON)
*(.rtc.bss)
mapping[rtc_bss]
_rtc_bss_end = ABSOLUTE(.);
} > rtc_data_location
@ -152,22 +157,9 @@ SECTIONS
{
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
*(.iram1 .iram1.*)
*libesp_ringbuf.a:(.literal .text .literal.* .text.*)
*libfreertos.a:(.literal .text .literal.* .text.*)
*libheap.a:multi_heap.*(.literal .text .literal.* .text.*)
*libheap.a:multi_heap_poisoning.*(.literal .text .literal.* .text.*)
*libesp32.a:panic.*(.literal .text .literal.* .text.*)
*libesp32.a:core_dump.*(.literal .text .literal.* .text.*)
*libapp_trace.a:(.literal .text .literal.* .text.*)
*libxtensa-debug-module.a:eri.*(.literal .text .literal.* .text.*)
*librtc.a:(.literal .text .literal.* .text.*)
*libsoc.a:rtc_*.*(.literal .text .literal.* .text.*)
*libsoc.a:cpu_util.*(.literal .text .literal.* .text.*)
*libhal.a:(.literal .text .literal.* .text.*)
*libgcc.a:lib2funcs.*(.literal .text .literal.* .text.*)
*libspi_flash.a:spi_flash_rom_patch.*(.literal .text .literal.* .text.*)
*libgcov.a:(.literal .text .literal.* .text.*)
mapping[iram0_text]
INCLUDE esp32.spiram.rom-functions-iram.ld
_iram_text_end = ABSOLUTE(.);
_iram_end = ABSOLUTE(.);
@ -187,8 +179,6 @@ SECTIONS
*libbtdm_app.a:(.data .data.*)
. = ALIGN (4);
_btdm_data_end = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d.*)
*(.data1)
*(.sdata)
@ -198,14 +188,9 @@ SECTIONS
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
*(.jcr)
*(.dram1 .dram1.*)
*libesp32.a:panic.*(.rodata .rodata.*)
*libphy.a:(.rodata .rodata.*)
*libsoc.a:rtc_clk.*(.rodata .rodata.*)
*libapp_trace.a:(.rodata .rodata.*)
*libgcov.a:(.rodata .rodata.*)
*libheap.a:multi_heap.*(.rodata .rodata.*)
*libheap.a:multi_heap_poisoning.*(.rodata .rodata.*)
mapping[dram0_data]
INCLUDE esp32.spiram.rom-functions-dram.ld
_data_end = ABSOLUTE(.);
. = ALIGN(4);
@ -239,6 +224,9 @@ SECTIONS
*libbtdm_app.a:(.bss .bss.* COMMON)
. = ALIGN (4);
_btdm_bss_end = ABSOLUTE(.);
mapping[dram0_bss]
*(.dynsbss)
*(.sbss)
*(.sbss.*)
@ -248,11 +236,9 @@ SECTIONS
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.bss)
*(.bss.*)
*(.share.mem)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN (8);
_bss_end = ABSOLUTE(.);
/* The heap starts right after end of this section */
@ -265,8 +251,9 @@ SECTIONS
.flash.rodata :
{
_rodata_start = ABSOLUTE(.);
*(.rodata)
*(.rodata.*)
mapping[flash_rodata]
*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
*(.gnu.linkonce.r.*)
*(.rodata1)
@ -324,7 +311,10 @@ SECTIONS
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
mapping[flash_text]
*(.stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
*(.fini.literal)
*(.fini)

View File

@ -0,0 +1,80 @@
[sections:text]
entries:
.text+
.literal+
[sections:data]
entries:
.data+
[sections:bss]
entries:
.bss+
[sections:common]
entries:
COMMON
[sections:rodata]
entries:
.rodata+
[sections:rtc_text]
entries:
.rtc.text
.rtc.literal
[sections:rtc_data]
entries:
.rtc.data
[sections:rtc_rodata]
entries:
.rtc.rodata
[sections:rtc_bss]
entries:
.rtc.bss
[sections:iram]
entries:
.iram1+
[sections:dram]
entries:
.dram1+
[scheme:default]
entries:
text -> flash_text
rodata -> flash_rodata
data -> dram0_data
bss -> dram0_bss
common -> dram0_bss
iram -> iram0_text
dram -> dram0_data
rtc_text -> rtc_text
rtc_data -> rtc_data
rtc_rodata -> rtc_data
rtc_bss -> rtc_bss
[scheme:rtc]
entries:
text -> rtc_text
data -> rtc_data
rodata -> rtc_data
bss -> rtc_bss
common -> rtc_bss
[scheme:noflash]
entries:
text -> iram0_text
rodata -> dram0_data
[scheme:noflash_data]
entries:
rodata -> dram0_data
[scheme:noflash_text]
entries:
text -> iram0_text

View File

@ -0,0 +1,30 @@
[mapping]
archive: libesp32.a
entries:
core_dump (noflash_text)
panic (noflash)
[mapping]
archive: libphy.a
entries:
* (noflash_data)
[mapping]
archive: libhal.a
entries:
* (noflash_text)
[mapping]
archive: librtc.a
entries:
* (noflash_text)
[mapping]
archive: libgcc.a
entries:
lib2funcs (noflash_text)
[mapping]
archive: libgcov.a
entries:
* (noflash)

View File

@ -1,5 +1,6 @@
set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_SRCS "ringbuf.c")
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
set(COMPONENT_REQUIRES)

View File

@ -0,0 +1 @@
COMPONENT_ADD_LDFRAGMENTS += linker.lf

View File

@ -0,0 +1,4 @@
[mapping]
archive: libesp_ringbuf.a
entries:
* (noflash_text)

View File

@ -20,7 +20,7 @@ set(COMPONENT_SRCS "FreeRTOS-openocd.c"
# app_trace is required by FreeRTOS headers only when CONFIG_SYSVIEW_ENABLE=y,
# but requirements can't depend on config options, so always require it.
set(COMPONENT_REQUIRES app_trace)
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
register_component()
target_link_libraries(freertos "-Wl,--undefined=uxTopUsedPriority")

View File

@ -7,3 +7,4 @@ COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := include/freertos
tasks.o event_groups.o timers.o queue.o: CFLAGS += -D_ESP_FREERTOS_INTERNAL
COMPONENT_ADD_LDFRAGMENTS += linker.lf

View File

@ -0,0 +1,5 @@
[mapping]
archive: libfreertos.a
entries:
* (noflash_text)

View File

@ -12,7 +12,7 @@ if(CONFIG_HEAP_TASK_TRACKING)
endif()
set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
set(COMPONENT_REQUIRES "")
register_component()

View File

@ -20,3 +20,5 @@ WRAP_ARGUMENT := -Wl,--wrap=
COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS))
endif
COMPONENT_ADD_LDFRAGMENTS += linker.lf

View File

@ -0,0 +1,5 @@
[mapping]
archive: libheap.a
entries:
multi_heap (noflash)
multi_heap_poisoning (noflash)

View File

@ -11,5 +11,8 @@ endif()
list(APPEND COMPONENT_ADD_INCLUDEDIRS include)
list(APPEND COMPONENT_SRCS "src/memory_layout_utils.c")
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
set(COMPONENT_REQUIRES)
register_component()

View File

@ -5,3 +5,4 @@ COMPONENT_SRCDIRS := $(SOC_NAME) src/
COMPONENT_ADD_INCLUDEDIRS := $(SOC_NAME)/include include
-include $(COMPONENT_PATH)/$(SOC_NAME)/component.mk
COMPONENT_ADD_LDFRAGMENTS += linker.lf

12
components/soc/linker.lf Normal file
View File

@ -0,0 +1,12 @@
[mapping]
archive: libsoc.a
entries:
cpu_util (noflash_text)
rtc_clk (noflash)
rtc_clk_init (noflash_text)
rtc_init (noflash_text)
rtc_periph (noflash_text)
rtc_pm (noflash_text)
rtc_sleep (noflash_text)
rtc_time (noflash_text)
rtc_wdt (noflash_text)

View File

@ -15,4 +15,6 @@ endif()
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_REQUIRES)
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
register_component()

View File

@ -1,7 +1,8 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_ADD_LDFRAGMENTS += linker.lf
ifdef IS_BOOTLOADER_BUILD
# Bootloader needs updated SPIUnlock from this file
COMPONENT_OBJS := spi_flash_rom_patch.o
endif

View File

@ -0,0 +1,5 @@
[mapping]
archive: libspi_flash.a
entries:
spi_flash_rom_patch (noflash_text)

View File

@ -4,4 +4,6 @@ set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_REQUIRES "")
set(COMPONENT_ADD_LDFRAGMENTS linker.lf)
register_component()

View File

@ -2,3 +2,4 @@
# Component Makefile
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_LDFRAGMENTS += linker.lf

View File

@ -0,0 +1,4 @@
[mapping]
archive: libxtensa-debug-module.a
entries:
eri (noflash_text)

View File

@ -350,6 +350,7 @@ The following variables can be set inside ``component.mk`` to control the build
``*.c``, ``*.S``). Source files are globbed from the listed directories and compiled as part of the component in place of ``COMPONENT_SRCS``, i.e. setting this will cause ``COMPONENT_SRCS`` to be ignored.
This can be a convenient way of including source files to the components en masse, but is generally not recommended due to caveats attached to CMake globbing (see `File Globbing & Incremental Builds`).
- ``COMPONENT_SRCEXCLUDE``: Paths to source files to exclude from component. Can be set in conjunction with ``COMPONENT_SRCDIRS`` if there is a directory with a large number of source files to include in the component but one or more source files which should not be. Paths can be specified relative to the component directory or absolute.
- ``COMPONENT_ADD_LDFRAGMENTS``: Paths to linker fragment files for the linker script generation functionality. See :doc:`Linker Script Generation <linker-script-generation>`.
.. note::
@ -688,6 +689,14 @@ Place this line after the ``project()`` line in your project CMakeLists.txt file
For an example of using this technique, see :example:`protocols/https_request` - the certificate file contents are loaded from the text .pem file at compile time.
Code and Data Placements
------------------------
ESP-IDF has a feature called linker script generation that enables components to define where its code and data will be placed in memory through
linker fragment files. These files are processed by the build system, and is used to augment the linker script used for linking
app binary. See :doc:`Linker Script Generation <linker-script-generation>` for a quick start guide as well as a detailed discussion
of the mechanism.
.. _component-build-full-override:
Fully Overriding The Component Build Process

View File

@ -274,6 +274,8 @@ The following variables can be set inside ``component.mk`` to control the build
settings. Component-specific additions can be made via ``CXXFLAGS
+=``. It is also possible (although not recommended) to override
this variable completely for a component.
- ``COMPONENT_ADD_LDFRAGMENTS``: Paths to linker fragment files for the linker
script generation functionality. See :doc:`Linker Script Generation <linker-script-generation>`.
To apply compilation flags to a single source file, you can add a variable override as a target, ie::
@ -570,6 +572,13 @@ The names are generated from the full name of the file, as given in COMPONENT_EM
For an example of using this technique, see :example:`protocols/https_request` - the certificate file contents are loaded from the text .pem file at compile time.
Code and Data Placements
------------------------
ESP-IDF has a feature called linker script generation that enables components to define where its code and data will be placed in memory through
linker fragment files. These files are processed by the build system, and is used to augment the linker script used for linking
app binary. See :doc:`Linker Script Generation <linker-script-generation>` for a quick start guide as well as a detailed discussion
of the mechanism.
Fully Overriding The Component Makefile
---------------------------------------

View File

@ -30,3 +30,4 @@ API Guides
ESP-MESH <mesh>
BluFi <blufi>
External SPI-connected RAM <external-ram>
Linker Script Generation <linker-script-generation>

View File

@ -0,0 +1,546 @@
Linker Script Generation
========================
Overview
--------
There are several :ref:`memory regions<memory-layout>` where code and data can be placed. Usually, code and read-only data are placed in flash regions,
writable data in RAM, etc. A common action is changing where code/data are mapped by default, say placing critical code/rodata in RAM for performance
reasons or placing code/data/rodata in RTC memory for use in a wake stub or the ULP coprocessor.
IDF provides the ability for defining these placements at the component level using the linker script generation mechanism. The component presents
how it would like to map the input sections of its object files (or even functions/data) through :ref:`linker fragment files<ldgen-fragment-files>`. During app build,
the linker fragment files are collected, parsed and processed; and the :ref:`linker script template<ldgen-script-templates>` is augmented with
information generated from the fragment files to produce the final linker script. This linker script is then used for the linking
the final app binary.
Quick Start
------------
This section presents a guide for quickly placing code/data to RAM and RTC memory; as well as demonstrating how to make these placements
dependent on project configuration values. In a true quick start fashion, this section glosses over terms and concepts that will be discussed
at a later part of the document. However, whenever it does so, it provides a link to the relevant section on the first mention.
.. _ldgen-add-fragment-file :
Preparation
^^^^^^^^^^^
Make
""""
Create a linker fragment file inside the component directory, which is just a text file with a .lf extension. In order for the build system to collect your fragment file,
add an entry to it from the component, set the variable ``COMPONENT_ADD_LDFRAGMENTS`` to your linker file/s before the ``register_component`` call.
.. code-block:: make
# file paths relative to component Makefile
COMPONENT_ADD_LDFRAGMENTS += "path/to/linker_fragment_file.lf" "path/to/another_linker_fragment_file.lf"
CMake
"""""
For CMake set the variable ``COMPONENT_ADD_LDFRAGMENTS`` to your linker file/s before the ``register_component`` call.
.. code-block:: cmake
# file paths relative to CMakeLists.txt
set(COMPONENT_ADD_LDFRAGMENTS "path/to/linker_fragment_file.lf" "path/to/another_linker_fragment_file.lf")
register_component()
Specifying placements
^^^^^^^^^^^^^^^^^^^^^
This mechanism allows specifying placement of the following entities:
- one or multiple object files within the component
- one or multiple function/variable using their names
- the entire component library
For the following text, suppose we have the following:
- a component named ``component`` that is archived as library ``libcomponent.a`` during build
- three object files archived under the library, ``object1.o``, ``object2.o`` and ``object3.o``
- under ``object1.o``, the function ``function1`` is defined; under ``object2.o``, the function ``function2`` is defined
- there exists configuration ``PERFORMANCE_MODE`` and ``PERFORMANCE_LEVEL`` in one of the IDF KConfig files, with the set value indicated by entries ``CONFIG_PERFORMANCE_MODE`` and ``CONFIG_PERFORMANCE_LEVEL`` in the project sdkconfig
In the created linker fragment file, we write:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
This creates an empty :ref:`mapping fragment<ldgen-mapping-fragment>`, which doesn't do anything yet. During linking the :ref:`default placements<ldgen-default-placements>`
will still be used for ``libcomponent.a``, unless the ``entries`` key is populated.
.. _ldgen-placing-object-files :
Placing object files
""""""""""""""""""""
Suppose the entirety of ``object1.o`` is performance-critical, so it is desirable to place it in RAM. On the other hand, all of ``object2.o``
contains things to be executed coming out of deep sleep, so it needs to be put under RTC memory. We can write:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
object1 (noflash) # places all code / read-only data under IRAM/ DRAM
object2 (rtc) # places all code/ data and read-only data under RTC fast memory/ RTC slow memory
What happens to ``object3.o``? Since it is not specified, default placements are used for ``object3.o``.
Placing functions/data using their names
""""""""""""""""""""""""""""""""""""""""
Continuing our example, suppose that among functions defined under ``object1.o``, only ``function1`` is performance-critical; and under ``object2.o``,
only ``function2`` needs to execute after the chip comes out of deep sleep. This could be accomplished by writing:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
object1:function1 (noflash)
object2:function2 (rtc)
The default placements are used for the rest of the functions in ``object1.o`` and ``object2.o`` and the entire ``object3.o``. Something similar
can be achieved for placing data by writing the variable name instead of the function name after ``:``.
.. warning::
There are :ref:`limitations<ldgen-type3-limitations>` in placing code/data using their symbol names. In order to ensure proper placements, an alternative would be to group
relevant code and data into source files, and :ref:`use object file placement<ldgen-placing-object-files>`.
Placing entire component
""""""""""""""""""""""""
In this example, suppose that the entire component needs to be placed in RAM. This can be written as:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
* (noflash)
Similarly, this places the entire component in RTC memory:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
* (rtc)
Configuration-dependent placements
""""""""""""""""""""""""""""""""""
Suppose that the entire component library should only be placed when ``CONFIG_PERFORMANCE_MODE == y`` in the sdkconfig. This could be written as:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
: PERFORMANCE_MODE = y
* (noflash)
In pseudocode, this translates to:
.. code-block:: none
if PERFORMANCE_MODE = y
place entire libcomponent.a in RAM
else
use default placements
It is also possible to have multiple conditions to test. Suppose the following requirements: when ``CONFIG_PERFORMANCE_LEVEL == 1``, only ``object1.o`` is put in RAM;
when ``CONFIG_PERFORMANCE_LEVEL == 2``, ``object1.o`` and ``object2.o``; and when ``CONFIG_PERFORMANCE_LEVEL == 3`` all object files under the archive
are to be put into RAM. When these three are false however, put entire library in RTC memory. This scenario is a bit contrived, but,
it can be written as:
.. code-block:: none
[mapping]
archive: libcomponent.a
entries:
: PERFORMANCE_LEVEL = 3
* (noflash)
: PERFORMANCE_LEVEL = 2
object1 (noflash)
object2 (noflash)
: PERFORMANCE_LEVEL = 1
object1 (noflash)
: default
* (rtc)
Which reads:
.. code-block:: none
if CONFIG_PERFORMANCE_LEVEL == 3
place entire libcomponent.a in RAM
else if CONFIG_PERFORMANCE_LEVEL == 2
only place object1.o and object2.o in RAM
else if CONFIG_PERFORMANCE_LEVEL == 1
only place object1.o in RAM
else
place entire libcomponent.a in RTC memory
The conditions test :ref:`support other operations<ldgen-condition-entries>`.
.. _ldgen-default-placements:
The 'default' placements
^^^^^^^^^^^^^^^^^^^^^^^^
Up until this point, the term 'default placements' has been mentioned as fallback placements for when the
placement rules ``rtc`` and ``noflash`` are not specified. The tokens ``noflash`` or ``rtc`` are not merely keywords known by the mechanism, but are actually
objects called :ref:`scheme fragments<ldgen-scheme-fragment>` that are specified by the user. Due to the commonness of these placement use cases,
they are pre-defined in IDF.
Similarly, there exists a ``default`` scheme fragment which defines what the default placement rules should be, which is discussed :ref:`here<ldgen-default-scheme>`.
.. note::
For an example of an IDF component using this feature, see :component_file:`freertos/CMakeLists.txt`. The ``freertos`` component uses this
mechanism to place all code, literal and rodata of all of its object files to the instruction RAM memory region for performance reasons.
This marks the end of the quick start guide. The following text discusses this mechanism in a little bit more detail, such its components, essential concepts,
the syntax, how it is integrated with the build system, etc. The following sections should be helpful in creating custom mappings or modifying default
behavior.
Components
----------
.. _ldgen-fragment-files :
Linker Fragment Files
^^^^^^^^^^^^^^^^^^^^^
The fragment files contain objects called 'fragments'. These fragments contain pieces of information which, when put together, form
placement rules that tell where to place sections of object files in the output binary.
Another way of putting it is that processing linker fragment files aims to create the section placement rules inside GNU LD ``SECTIONS`` command.
Where to collect and put these section placement rules is represented internally as a ``target`` token.
The three types of fragments are discussed below.
.. note::
Fragments have a name property (except mapping fragments) and are known globally.
Fragment naming follows C variable naming rules, i.e. case sensitive, must begin with a letter or underscore, alphanumeric/underscore after
initial characters are allowed, no spaces/special characters. Each type of fragment has its own namespace. In cases where multiple fragments
of the same type and name are encountered, an exception is thrown.
.. _ldgen-sections-fragment :
I. Sections
"""""""""""
Sections fragments defines a list of object file sections that the GCC compiler emits. It may be a default section (e.g. ``.text``, ``.data``) or
it may be user defined section through the ``__attribute__`` keyword.
The use of an optional '+' indicates the inclusion of the section in the list, as well as sections that start with it. This is the preferred method over listing both explicitly.
**Syntax**
.. code-block:: none
[sections:name]
entries:
.section+
.section
...
**Example**
.. code-block:: none
# Non-preferred
[sections:text]
entries:
.text
.text.*
.literal
.literal.*
# Preferred, equivalent to the one above
[sections:text]
entries:
.text+ # means .text and .text.*
.literal+ # means .literal and .literal.*
.. _ldgen-scheme-fragment :
II. Scheme
""""""""""
Scheme fragments define what ``target`` a sections fragment is assigned to.
**Syntax**
.. code-block:: none
[scheme:name]
entries:
sections -> target
sections -> target
...
**Example**
.. code-block:: none
[scheme:noflash]
entries:
text -> iram0_text # the entries under the sections fragment named text will go to iram0_text
rodata -> dram0_data # the entries under the sections fragment named rodata will go to dram0_data
.. _ldgen-default-scheme:
**The** ``default`` **scheme**
There exists a special scheme with the name ``default``. This scheme is special because catch-all placement rules are generated from
its entries. This means that, if one of its entries is ``text -> flash_text``, the placement rule
.. code-block:: none
*(.literal .literal.* .text .text.*)
will be generated for the target ``flash_text``.
These catch-all rules then effectively serve as fallback rules for those whose mappings were not specified.
.. note::
The ``default scheme`` is defined in :component:`esp32/ld/esp32_fragments.lf`. The ``noflash`` and ``rtc`` scheme fragments which are
built-in schemes referenced in the quick start guide are also defined in this file.
.. _ldgen-mapping-fragment :
III. Mapping
""""""""""""
Mapping fragments define what scheme fragment to use for mappable entities, i.e. object files, function names, variable names. There are two types of entries
for this fragment: mapping entries and condition entries.
.. note::
Mapping fragments have no explicit name property. Internally, the name is constructed from the value of the archive entry.
**Syntax**
.. code-block:: none
[mapping]
archive: archive # output archive file name, as built (i.e. libxxx.a)
entries:
: condition # condition entry, non-default
object:symbol (scheme) # mapping entry, Type I
object (scheme) # mapping entry, Type II
* (scheme) # mapping entry, Type III
# optional separation/comments, for readability
: default # condition entry, default
* (scheme) # mapping entry, Type III
.. _ldgen-mapping-entries :
**Mapping Entries**
There are three types of mapping entries:
``Type I``
The object file name and symbol name are specified. The symbol name can be a function name or a variable name.
``Type II``
Only the object file name is specified.
``Type III``
``*`` is specified, which is a short-hand for all the object files under the archive.
To know what a mapping entry means, let us expand a ``Type II`` entry. Originally:
.. code-block:: none
object (scheme)
Then expanding the scheme fragment from its entries definitions, we have:
.. code-block:: none
object (sections -> target,
sections -> target,
...)
Expanding the sections fragment with its entries definition:
.. code-block:: none
object (.section, # given this object file
.section, # put its sections listed here at this
... -> target, # target
.section,
.section, # same should be done for these sections
... -> target,
...) # and so on
.. _ldgen-type3-limitations :
**On** ``Type I`` **Mapping Entries**
``Type I`` mapping entry is possible due to compiler flags ``-ffunction-sections`` and ``-ffdata-sections``. If the user opts to remove these flags, then
the ``Type I`` mapping will not work. Furthermore, even if the user does not opt to compile without these flags, there are still limitations
as the implementation is dependent on the emitted output sections.
For example, with ``-ffunction-sections``, separate sections are emitted for each function; with section names predictably constructed i.e. ``.text.{func_name}``
and ``.literal.{func_name}``. This is not the case for string literals within the function, as they go to pooled or generated section names.
With ``-fdata-sections``, for global scope data the compiler predictably emits either ``.data.{var_name}``, ``.rodata.{var_name}`` or ``.bss.{var_name}``; and so ``Type I`` mapping entry works for these.
However, this is not the case for static data declared in function scope, as the generated section name is a result of mangling the variable name with some other information.
.. _ldgen-condition-entries :
**Condition Entries**
Condition entries enable the linker script generation to be configuration-aware. Depending on whether expressions involving configuration values
are true or not, a particular set of mapping entries can be used. The evaluation uses ``eval_string`` from ``:idf_file:`tools/kconfig_new/kconfiglib.py``` and adheres to its required syntax and limitations.
All mapping entries defined after a condition entry until the next one or the end of the mapping fragment belongs to that condition entry. During processing
conditions are tested sequentially, and the mapping entries under the first condition that evaluates to ``TRUE`` are used.
A default condition can be defined (though every mapping contains an implicit, empty one), whose mapping entries get used in the event no conditions evaluates to ``TRUE``.
**Example**
.. code-block:: none
[scheme:noflash]
entries:
text -> iram0_text
rodata -> dram0_data
[mapping:lwip]
archive: liblwip.a
entries:
: LWIP_IRAM_OPTIMIZATION = y # if CONFIG_LWIP_IRAM_OPTIMIZATION is set to 'y' in sdkconfig
ip4:ip4_route_src_hook (noflash) # map ip4.o:ip4_route_src_hook, ip4.o:ip4_route_src and
ip4:ip4_route_src (noflash) # ip4.o:ip4_route using the noflash scheme, which puts
ip4:ip4_route (noflash) # them in RAM
: default # else no special mapping rules apply
.. _ldgen-script-templates :
Linker Script Template
^^^^^^^^^^^^^^^^^^^^^^
The linker script template is the skeleton in which the generated placement rules are put into. It is an otherwise ordinary linker script, with a specific marker syntax
that indicates where the generated placement rules are placed.
**Syntax**
To reference the placement rules collected under a ``target`` token, the following syntax is used:
.. code-block:: none
mapping[target]
**Example**
The example below is an excerpt from a possible linker script template. It defines an output section ``.iram0.text``, and inside is a marker referencing
the target ``iram0_text``.
.. code-block:: none
.iram0.text :
{
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
/* Marker referencing iram0_text */
mapping[iram0_text]
INCLUDE esp32.spiram.rom-functions-iram.ld
_iram_text_end = ABSOLUTE(.);
} > iram0_0_seg
Suppose the generator collected the fragment definitions below:
.. code-block:: none
[sections:text]
.text+
.literal+
[sections:iram]
.iram1+
[scheme:default]
entries:
text -> flash_text
iram -> iram0_text
[scheme:noflash]
entries:
text -> iram0_text
[mapping:freertos]
archive: libfreertos.a
entries:
* (noflash)
Then the corresponding excerpt from the generated linker script will be as follows:
.. code-block:: c
.iram0.text :
{
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
/* Placement rules generated from the processed fragments, placed where the marker was in the template */
*(.iram1 .iram1.*)
*libfreertos.a:(.literal .text .literal.* .text.*)
INCLUDE esp32.spiram.rom-functions-iram.ld
_iram_text_end = ABSOLUTE(.);
} > iram0_0_seg
``*libfreertos.a:(.literal .text .literal.* .text.*)``
Rule generated from the entry ``* (noflash)`` of the ``freertos`` mapping fragment. All ``text`` sections of all
object files under the archive ``libfreertos.a`` will be collected under the target ``iram0_text`` (as per the ``noflash`` scheme)
and placed wherever in the template ``iram0_text`` is referenced by a marker.
``*(.iram1 .iram1.*)``
Rule generated from the default scheme entry ``iram -> iram0_text``. Since the default scheme specifies an ``iram -> iram0_text`` entry,
it too is placed wherever ``iram0_text`` is referenced by a marker. Since it is a rule generated from the default scheme, it comes first
among all other rules collected under the same target name.
Integration with Build System
-----------------------------
The linker script generation occurs during application build, before the final output binary is linked. The tool that implements the mechanism
lives under ``$(IDF_PATH)/tools/ldgen``.
Linker Script Template
^^^^^^^^^^^^^^^^^^^^^^
Currently, the linker script template used is :component:`esp32/ld/esp32.common.ld.in`, and is used only for the app build. The generated output script is
put under the build directory of the same component. Modifying this linker script template triggers a re-link of the app binary.
Linker Fragment File
^^^^^^^^^^^^^^^^^^^^
Any component can add a fragment file to the build. In order to add a fragment file to process, use the command ``ldgen_add_fragment_file`` as mentioned :ref:`here<ldgen-add-fragment-file>`.
Modifying any fragment file presented to the build system triggers a re-link of the app binary.

View File

@ -30,3 +30,4 @@ API 指南
ESP-MESH <mesh>
BluFi <blufi>
External SPI-connected RAM <external-ram>
Linker Script Generation <linker-script-generation>

View File

@ -0,0 +1 @@
.. include:: ../../en/api-guides/linker-script-generation.rst

View File

@ -46,6 +46,10 @@ COMPONENT_EMBED_TXTFILES ?=
COMPONENT_ADD_INCLUDEDIRS = include
COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME)
# Name of the linker fragment files this component presents to the Linker
# script generator
COMPONENT_ADD_LDFRAGMENTS ?=
# Define optional compiling macros
define compile_exclude
COMPONENT_OBJEXCLUDE += $(1)
@ -151,6 +155,8 @@ OWN_INCLUDES:=$(abspath $(addprefix $(COMPONENT_PATH)/,$(COMPONENT_PRIV_INCLUDED
COMPONENT_INCLUDES := $(OWN_INCLUDES) $(filter-out $(OWN_INCLUDES),$(COMPONENT_INCLUDES))
include $(IDF_PATH)/make/ldgen.mk
################################################################################
# 4) Define a target to generate component_project_vars.mk Makefile which
# contains common per-component settings which are included directly in the
@ -193,8 +199,8 @@ component_project_vars.mk::
@echo 'COMPONENT_LINKER_DEPS += $(call MakeVariablePath,$(call resolvepath,$(COMPONENT_ADD_LINKER_DEPS),$(COMPONENT_PATH)))' >> $@
@echo 'COMPONENT_SUBMODULES += $(call MakeVariablePath,$(abspath $(addprefix $(COMPONENT_PATH)/,$(COMPONENT_SUBMODULES))))' >> $@
@echo 'COMPONENT_LIBRARIES += $(COMPONENT_NAME)' >> $@
@echo 'COMPONENT_LDFRAGMENTS += $(call MakeVariablePath,$(abspath $(addprefix $(COMPONENT_PATH)/,$(COMPONENT_ADD_LDFRAGMENTS))))' >> $@
@echo 'component-$(COMPONENT_NAME)-build: $(addprefix component-,$(addsuffix -build,$(COMPONENT_DEPENDS)))' >> $@
################################################################################
# 5) Where COMPONENT_OWNBUILDTARGET / COMPONENT_OWNCLEANTARGET
# is not set by component.mk, define default build, clean, etc. targets
@ -212,7 +218,7 @@ build: $(COMPONENT_LIBRARY)
$(COMPONENT_LIBRARY): $(COMPONENT_OBJS) $(COMPONENT_EMBED_OBJS)
$(summary) AR $(patsubst $(PWD)/%,%,$(CURDIR))/$@
rm -f $@
$(AR) $(ARFLAGS) $@ $^
$(AR) $(ARFLAGS) $@ $(COMPONENT_OBJS) $(COMPONENT_EMBED_OBJS)
endif
# If COMPONENT_OWNCLEANTARGET is not set, define a phony clean target
@ -332,7 +338,7 @@ clean:
$(summary) RM component_project_vars.mk
rm -f component_project_vars.mk
component_project_vars.mk:: # no need to add variables via component.mk
component_project_vars.mk:: # no need to add variables via component.mk
@echo '# COMPONENT_CONFIG_ONLY target sets no variables here' > $@
endif # COMPONENT_CONFIG_ONLY

37
make/ldgen.mk Normal file
View File

@ -0,0 +1,37 @@
# Makefile to support the linker script generation mechanism
LDGEN_SECTIONS_INFO_FILES = $(foreach lib, $(COMPONENT_LIBRARIES), $(BUILD_DIR_BASE)/$(lib)/lib$(lib).a.sections_info)
LDGEN_FRAGMENT_FILES = $(COMPONENT_LDFRAGMENTS)
# Target to generate linker script generator from fragments presented by each of
# the components
define ldgen_process_template
$(2): $(1) $(LDGEN_FRAGMENT_FILES) $(SDKCONFIG) $(LDGEN_SECTIONS_INFO_FILES)
@echo 'Generating $(notdir $(2))'
$(PYTHON) $(IDF_PATH)/tools/ldgen/ldgen.py \
--input $(1) \
--config $(SDKCONFIG) \
--fragments $(LDGEN_FRAGMENT_FILES) \
--output $(2) \
--sections $(LDGEN_SECTIONS_INFO_FILES) \
--kconfig $(IDF_PATH)/Kconfig \
--env "COMPONENT_KCONFIGS=$(COMPONENT_KCONFIGS)" \
--env "COMPONENT_KCONFIGS_PROJBUILD=$(COMPONENT_KCONFIGS_PROJBUILD)" \
--env "IDF_CMAKE=n"
--env "IDF_TARGET=$(IDF_TARGET)"
endef
define ldgen_create_commands
$(foreach lib, $(COMPONENT_LIBRARIES), \
$(eval $(call ldgen_generate_target_sections_info, $(BUILD_DIR_BASE)/$(lib)/lib$(lib).a)))
ldgen-clean:
rm -f $(LDGEN_SECTIONS_INFO_FILES)
endef
# Target to generate sections info file from objdump of component archive
define ldgen_generate_target_sections_info
$(1).sections_info: $(1)
@echo 'Generating $(notdir $(1).sections_info)'
$(OBJDUMP) -h $(1) > $(1).sections_info
endef

View File

@ -204,6 +204,7 @@ COMPONENT_INCLUDES :=
COMPONENT_LDFLAGS :=
COMPONENT_SUBMODULES :=
COMPONENT_LIBRARIES :=
COMPONENT_LDFRAGMENTS :=
# COMPONENT_PROJECT_VARS is the list of component_project_vars.mk generated makefiles
# for each component.
@ -381,6 +382,7 @@ CC ?= gcc
LD ?= ld
AR ?= ar
OBJCOPY ?= objcopy
OBJDUMP ?= objdump
SIZE ?= size
# Set host compiler and binutils
@ -398,8 +400,9 @@ CXX := $(call dequote,$(CONFIG_TOOLPREFIX))c++
LD := $(call dequote,$(CONFIG_TOOLPREFIX))ld
AR := $(call dequote,$(CONFIG_TOOLPREFIX))ar
OBJCOPY := $(call dequote,$(CONFIG_TOOLPREFIX))objcopy
OBJDUMP := $(call dequote,$(CONFIG_TOOLPREFIX))objdump
SIZE := $(call dequote,$(CONFIG_TOOLPREFIX))size
export CC CXX LD AR OBJCOPY SIZE
export CC CXX LD AR OBJCOPY OBJDUMP SIZE
COMPILER_VERSION_STR := $(shell $(CC) -dumpversion)
COMPILER_VERSION_NUM := $(subst .,,$(COMPILER_VERSION_STR))
@ -416,6 +419,18 @@ APP_ELF:=$(BUILD_DIR_BASE)/$(PROJECT_NAME).elf
APP_MAP:=$(APP_ELF:.elf=.map)
APP_BIN:=$(APP_ELF:.elf=.bin)
# once we know component paths, we can include the config generation targets
#
# (bootloader build doesn't need this, config is exported from top-level)
ifndef IS_BOOTLOADER_BUILD
include $(IDF_PATH)/make/project_config.mk
endif
# include linker script generation utils makefile
include $(IDF_PATH)/make/ldgen.mk
$(eval $(call ldgen_create_commands))
# Include any Makefile.projbuild file letting components add
# configuration at the project level
define includeProjBuildMakefile
@ -427,13 +442,6 @@ $(foreach componentpath,$(COMPONENT_PATHS), \
$(if $(wildcard $(componentpath)/Makefile.projbuild), \
$(eval $(call includeProjBuildMakefile,$(componentpath)))))
# once we know component paths, we can include the config generation targets
#
# (bootloader build doesn't need this, config is exported from top-level)
ifndef IS_BOOTLOADER_BUILD
include $(IDF_PATH)/make/project_config.mk
endif
# ELF depends on the library archive files for COMPONENT_LIBRARIES
# the rules to build these are emitted as part of GenerateComponentTarget below
#
@ -538,7 +546,7 @@ endif
# _config-clean), so config remains valid during all component clean
# targets
config-clean: app-clean bootloader-clean
clean: app-clean bootloader-clean config-clean
clean: app-clean bootloader-clean config-clean ldgen-clean
# phony target to check if any git submodule listed in COMPONENT_SUBMODULES are missing
# or out of date, and exit if so. Components can add paths to this variable.

View File

@ -64,3 +64,6 @@ docs/gen-dxd.py
tools/ci/multirun_with_pyenv.sh
components/espcoredump/test/test_espcoredump.py
components/espcoredump/test/test_espcoredump.sh
tools/ldgen/ldgen.py
tools/ldgen/test/test_fragments.py
tools/ldgen/test/test_generation.py

View File

@ -157,9 +157,16 @@ function run_tests()
make
assert_rebuilt ${APP_BINS} ${BOOTLOADER_BINS}
print_status "Touching app-only ld file should only re-link app"
print_status "Touching app-only template ld file should only re-link app"
take_build_snapshot
touch ${IDF_PATH}/components/esp32/ld/esp32.common.ld
touch ${IDF_PATH}/components/esp32/ld/esp32.common.ld.in
make
assert_rebuilt ${APP_BINS}
assert_not_rebuilt ${BOOTLOADER_BINS}
print_status "Touching a linker fragment file should trigger re-link of app" # only app linker script is generated by tool for now
take_build_snapshot
touch ${IDF_PATH}/components/esp32/linker.lf
make
assert_rebuilt ${APP_BINS}
assert_not_rebuilt ${BOOTLOADER_BINS}

View File

@ -143,13 +143,23 @@ function run_tests()
print_status "Updating app-only ld file should only re-link app"
take_build_snapshot
cp ${IDF_PATH}/components/esp32/ld/esp32.common.ld .
cp ${IDF_PATH}/components/esp32/ld/esp32.common.ld.in .
sleep 1 # ninja may ignore if the timestamp delta is too low
echo "/* (Build test comment) */" >> ${IDF_PATH}/components/esp32/ld/esp32.common.ld
echo "/* (Build test comment) */" >> ${IDF_PATH}/components/esp32/ld/esp32.common.ld.in
idf.py build || failure "Failed to rebuild with modified linker script"
assert_rebuilt ${APP_BINS}
assert_not_rebuilt ${BOOTLOADER_BINS}
mv esp32.common.ld ${IDF_PATH}/components/esp32/ld/
mv esp32.common.ld.in ${IDF_PATH}/components/esp32/ld/
print_status "Updating fragment file should only re-link app" # only app linker script is generated by tool for now
take_build_snapshot
cp ${IDF_PATH}/components/esp32/ld/esp32_fragments.lf .
sleep 1 # ninja may ignore if the timestamp delta is too low
echo "# (Build test comment)" >> ${IDF_PATH}/components/esp32/ld/esp32_fragments.lf
idf.py build || failure "Failed to rebuild with modified linker fragment file"
assert_rebuilt ${APP_BINS}
assert_not_rebuilt ${BOOTLOADER_BINS}
mv esp32_fragments.lf ${IDF_PATH}/components/esp32/ld/
print_status "sdkconfig update triggers full recompile"
clean_build_dir

View File

@ -107,6 +107,11 @@ function(register_component)
target_link_libraries(${component} "-L${CMAKE_CURRENT_BINARY_DIR}")
target_link_libraries(${component} "-Wl,--whole-archive -l${component} -Wl,--no-whole-archive")
endif()
if(COMPONENT_ADD_LDFRAGMENTS)
spaces2list(COMPONENT_ADD_LDFRAGMENTS)
ldgen_add_fragment_files(${component} "${COMPONENT_ADD_LDFRAGMENTS}")
endif()
endfunction()
function(register_config_only_component)

72
tools/cmake/ldgen.cmake Normal file
View File

@ -0,0 +1,72 @@
# Utilities for supporting linker script generation in the build system
# ldgen_create_target
#
# Create the custom target to attach the fragment files and template files
# for the build to.
function(ldgen_set_variables)
add_custom_target(ldgen_section_infos)
add_custom_target(ldgen DEPENDS ldgen_section_infos)
endfunction()
# ldgen_add_fragment_file
#
# Add one or more linker fragment files, and append it to the list of fragment
# files found so far.
function(ldgen_add_fragment_files target fragment_files)
spaces2list(fragment_files)
foreach(fragment_file ${fragment_files})
get_filename_component(fragment_file_abs_dir ${fragment_file} ABSOLUTE BASE_DIR ${component_dir})
list(APPEND fragment_files_full_path ${fragment_file_abs_dir})
endforeach()
set_property(TARGET ldgen APPEND PROPERTY FRAGMENT_FILES ${fragment_files_full_path})
get_filename_component(target_sections_info ${CMAKE_CURRENT_BINARY_DIR}/${target}.sections_info ABSOLUTE)
add_custom_command(
OUTPUT ${target_sections_info}
COMMAND ${CMAKE_OBJDUMP} $<TARGET_FILE:${target}> -h > ${target_sections_info}
DEPENDS ${target}
)
add_custom_target(${target}_sections_info DEPENDS ${target_sections_info})
add_dependencies(ldgen_section_infos ${target}_sections_info)
set_property(TARGET ldgen_section_infos APPEND PROPERTY SECTIONS_INFO_FILES ${target_sections_info})
endfunction()
# ldgen_process_template
#
# Passes a linker script template to the linker script generation tool for
# processing
function(ldgen_process_template template output)
# Create command to invoke the linker script generator tool.
add_custom_command(
OUTPUT ${output}
COMMAND ${IDF_PATH}/tools/ldgen/ldgen.py
--config ${SDKCONFIG}
--fragments "$<JOIN:$<TARGET_PROPERTY:ldgen,FRAGMENT_FILES>,\t>"
--input ${template}
--output ${output}
--sections "$<JOIN:$<TARGET_PROPERTY:ldgen_section_infos,SECTIONS_INFO_FILES>,\t>"
--kconfig ${IDF_PATH}/Kconfig
--env "COMPONENT_KCONFIGS=${COMPONENT_KCONFIGS}"
--env "COMPONENT_KCONFIGS_PROJBUILD=${COMPONENT_KCONFIGS_PROJBUILD}"
--env "IDF_CMAKE=y"
--env "IDF_TARGET=${IDF_TARGET}"
DEPENDS ${template} $<TARGET_PROPERTY:ldgen,FRAGMENT_FILES> ${SDKCONFIG} ldgen_section_infos
)
get_filename_component(output_name ${output} NAME)
add_custom_target(ldgen_${output_name}_script DEPENDS ${output})
add_dependencies(ldgen ldgen_${output_name}_script)
endfunction()
# ldgen_create_commands
#
# Create the command to generate the output scripts from templates presented.
function(ldgen_add_dependencies executable_name)
add_dependencies(${executable_name} ldgen)
endfunction()

View File

@ -27,6 +27,7 @@ include(targets)
include(kconfig)
include(git_submodules)
include(idf_functions)
include(ldgen)
set_default(PYTHON "python")
@ -131,6 +132,16 @@ macro(project name)
## if project uses git, retrieve revision
git_describe(PROJECT_VER "${CMAKE_CURRENT_SOURCE_DIR}")
#
# Add the app executable to the build (has name of PROJECT.elf)
#
idf_add_executable()
#
# Setup variables for linker script generation
#
ldgen_set_variables()
# Include any top-level project_include.cmake files from components
foreach(component ${BUILD_COMPONENT_PATHS})
set(COMPONENT_PATH "${component}")
@ -156,10 +167,9 @@ macro(project name)
unset(COMPONENT_NAME)
unset(COMPONENT_PATH)
#
# Add the app executable to the build (has name of PROJECT.elf)
#
idf_add_executable()
# At this point the fragment files have been collected, generate
# the commands needed to generate the output linker scripts
ldgen_add_dependencies(${PROJECT_NAME}.elf)
# Write project description JSON file
make_json_list("${BUILD_COMPONENTS}" build_components_json)

View File

@ -85,6 +85,9 @@ function(require_idf_targets)
endif()
endfunction()
# Dummy call for ldgen_add_fragment_file
function(ldgen_add_fragment_file files)
endfunction()
# expand_component_requirements: Recursively expand a component's requirements,
# setting global properties BUILD_COMPONENTS & BUILD_COMPONENT_PATHS and

0
tools/ldgen/__init__.py Normal file
View File

249
tools/ldgen/fragments.py Normal file
View File

@ -0,0 +1,249 @@
#!/usr/bin/env python
#
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import collections
import sys
import os
from sdkconfig import SDKConfig
from pyparsing import *
"""
Fragment file internal representation. Parses and stores instances of the fragment definitions
contained within the file.
"""
class FragmentFileModel():
def __init__(self, fragment_file):
path = os.path.realpath(fragment_file.name)
sections = Sections.get_fragment_grammar()
scheme = Scheme.get_fragment_grammar()
mapping = Mapping.get_fragment_grammar()
# Each fragment file is composed of sections, scheme or mapping fragments. The grammar
# for each of those objects are defined it the respective classes
parser = OneOrMore(sections | scheme | mapping)
# Set any text beginnning with # as comment
parser.ignore("#" + restOfLine)
self.fragments = parser.parseFile(fragment_file, parseAll=True)
for fragment in self.fragments:
fragment.path = path
"""
Encapsulates a fragment as defined in the generator syntax. Sets values common to all fragment and performs processing
such as checking the validity of the fragment name and getting the entry values.
"""
class Fragment:
IDENTIFIER = Word(alphas+"_", alphanums+"_")
ENTITY = Word(alphanums + ".-_$")
def __init__(self, name, entries):
self.path = None
self.name = name
self.entries = entries
class Sections(Fragment):
def __init__(self, name, entries):
Fragment.__init__(self, name, entries)
self._process_entries()
def _process_entries(self):
# Quietly ignore duplicate entries
self.entries = set(self.entries)
self.entries = list(self.entries)
"""
Utility function that returns a list of sections given a sections fragment entry,
with the '+' notation and symbol concatenation handled automatically.
"""
@staticmethod
def get_section_data_from_entry(sections_entry, symbol=None):
if not symbol:
sections = list()
sections.append(sections_entry.replace("+", ""))
sections.append(sections_entry.replace("+", ".*"))
return sections
else:
if sections_entry.endswith("+"):
section = sections_entry.replace("+", ".*")
expansion = section.replace(".*", "." + symbol)
return (section, expansion)
else:
return (sections_entry, None)
@staticmethod
def get_fragment_grammar():
name = Fragment.IDENTIFIER
header = Suppress("[") + Suppress("sections") + Suppress(":") + name.setResultsName("name") + Suppress("]")
entry = Word(alphanums + "+" + ".")
entries = Suppress("entries") + Suppress(":") + Group(OneOrMore(entry)).setResultsName("entries")
sections = Group(header + entries)
sections.setParseAction(lambda t: Sections(t[0].name, t[0].entries))
sections.ignore("#" + restOfLine)
return sections
"""
Encapsulates a scheme fragment, which defines what target input sections are placed under.
"""
class Scheme(Fragment):
def __init__(self, name, items):
Fragment.__init__(self, name, items)
self._process_entries()
def _process_entries(self):
processed = set()
# Store entries as a set of tuples. Quietly ignores duplicate entries.
for entry in self.entries:
processed.add((entry.sections, entry.target))
self.entries = processed
@staticmethod
def get_fragment_grammar():
name = Fragment.IDENTIFIER
header = Suppress("[") + Suppress("scheme") + Suppress(":") + name.setResultsName("name") + Suppress("]")
# Scheme entry in the form 'sections -> target'
sections = Fragment.IDENTIFIER
target = Fragment.IDENTIFIER
entry = Group(sections.setResultsName("sections") + Suppress("->") + target.setResultsName("target"))
entries = Suppress("entries") + Suppress(":") + Group(OneOrMore(entry)).setResultsName("entries")
scheme = Group(header + entries)
scheme.setParseAction(lambda t: Scheme(t[0].name, t[0].entries))
scheme.ignore("#" + restOfLine)
return scheme
"""
Encapsulates a mapping fragment, which defines what targets the input sections of mappable entties are placed under.
"""
class Mapping(Fragment):
# Name of the default condition entry
DEFAULT_CONDITION = "default"
MAPPING_ALL_OBJECTS = "*"
def __init__(self, archive, entries):
self.archive = archive
# Generate name from archive value by replacing all non-alphanumeric
# characters with underscore
name = Mapping.get_mapping_name_from_archive(self.archive)
Fragment.__init__(self, name, entries)
self._process_entries()
def _create_mappings_set(self, mappings):
mapping_set = set()
for mapping in mappings:
obj = mapping.object
symbol = mapping.symbol
scheme = mapping.scheme
if symbol == "":
symbol = None
# Quietly handle duplicate definitions under the same condition
mapping_set.add((obj, symbol, scheme))
return mapping_set
def _process_entries(self):
processed = []
for normal_group in self.entries.normal_groups:
# Get the original string of the condition
condition = next(iter(normal_group.condition.asList())).strip()
mappings = self._create_mappings_set(normal_group.mappings)
processed.append((condition, mappings))
default_group = self.entries.default_group
mappings = self._create_mappings_set(default_group.mappings)
processed.append(("default", mappings))
self.entries = processed
@staticmethod
def get_mapping_name_from_archive(archive):
return re.sub(r"[^0-9a-zA-Z]+", "_", archive)
@staticmethod
def get_fragment_grammar():
# Match header [mapping]
header = Suppress("[") + Suppress("mapping") + Suppress("]")
# Define possbile values for input archive and object file
filename = Word(alphanums + "-" + "_")
# There are three possible patterns for mapping entries:
# obj:symbol (scheme)
# obj (scheme)
# * (scheme)
obj = Fragment.ENTITY.setResultsName("object")
symbol = Suppress(":") + Fragment.IDENTIFIER.setResultsName("symbol")
scheme = Suppress("(") + Fragment.IDENTIFIER.setResultsName("scheme") + Suppress(")")
pattern1 = Group(obj + symbol + scheme)
pattern2 = Group(obj + scheme)
pattern3 = Group(Literal(Mapping.MAPPING_ALL_OBJECTS).setResultsName("object") + scheme)
mapping_entry = pattern1 | pattern2 | pattern3
# To simplify parsing, classify groups of condition-mapping entry into two types: normal and default
# A normal grouping is one with a non-default condition. The default grouping is one which contains the
# default condition
mapping_entries = Group(ZeroOrMore(mapping_entry)).setResultsName("mappings")
normal_condition = Suppress(":") + originalTextFor(SDKConfig.get_expression_grammar())
default_condition = Optional(Suppress(":") + Literal(Mapping.DEFAULT_CONDITION))
normal_group = Group(normal_condition.setResultsName("condition") + mapping_entries)
default_group = Group(default_condition + mapping_entries).setResultsName("default_group")
normal_groups = Group(ZeroOrMore(normal_group)).setResultsName("normal_groups")
# Any mapping fragment definition can have zero or more normal group and only one default group as a last entry.
archive = Suppress("archive") + Suppress(":") + Fragment.ENTITY.setResultsName("archive")
entries = Suppress("entries") + Suppress(":") + (normal_groups + default_group).setResultsName("entries")
mapping = Group(header + archive + entries)
mapping.setParseAction(lambda t: Mapping(t[0].archive, t[0].entries))
mapping.ignore("#" + restOfLine)
return mapping

643
tools/ldgen/generation.py Normal file
View File

@ -0,0 +1,643 @@
#!/usr/bin/env python
#
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import collections
import itertools
import os
import subprocess
import fnmatch
from sdkconfig import SDKConfig
from fragments import FragmentFileModel, Sections, Scheme, Mapping, Fragment
from pyparsing import *
"""
Encapsulates a generated placement rule placed under a target
"""
class PlacementRule():
DEFAULT_SPECIFICITY = 0
ARCHIVE_SPECIFICITY = 1
OBJECT_SPECIFICITY = 2
SYMBOL_SPECIFICITY = 3
class __container():
def __init__(self, content):
self.content = content
__metadata = collections.namedtuple("__metadata", "excludes expansions expanded")
def __init__(self, archive, obj, symbol, sections, target):
if archive == "*":
archive = None
if obj == "*":
obj = None
self.archive = archive
self.obj = obj
self.symbol = symbol
self.target = target
self.sections = dict()
self.specificity = 0
self.specificity += 1 if self.archive else 0
self.specificity += 1 if (self.obj and not self.obj == '*') else 0
self.specificity += 1 if self.symbol else 0
for section in sections:
section_data = Sections.get_section_data_from_entry(section, self.symbol)
if not self.symbol:
for s in section_data:
metadata = self.__metadata(self.__container([]), self.__container([]), self.__container(False))
self.sections[s] = metadata
else:
(section, expansion) = section_data
if expansion:
metadata = self.__metadata(self.__container([]), self.__container([expansion]), self.__container(True))
self.sections[section] = metadata
def get_section_names(self):
return self.sections.keys()
def add_exclusion(self, other, sections_infos = None):
# Utility functions for this method
def do_section_expansion(rule, section):
if section in rule.get_section_names():
sections_in_obj = sections_infos.get_obj_sections(rule.archive, rule.obj)
expansions = fnmatch.filter(sections_in_obj, section)
return expansions
def remove_section_expansions(rule, section, expansions):
existing_expansions = self.sections[section].expansions.content
self.sections[section].expansions.content = [e for e in existing_expansions if e not in expansions]
# Exit immediately if the exclusion to be added is more general than this rule.
if not other.is_more_specific_rule_of(self):
return
for section in self.get_sections_intersection(other):
if(other.specificity == PlacementRule.SYMBOL_SPECIFICITY):
# If this sections has not been expanded previously, expand now and keep track.
previously_expanded = self.sections[section].expanded.content
if not previously_expanded:
expansions = do_section_expansion(self, section)
if expansions:
self.sections[section].expansions.content = expansions
self.sections[section].expanded.content = True
previously_expanded = True
# Remove the sections corresponding to the symbol name
remove_section_expansions(self, section, other.sections[section].expansions.content)
# If it has been expanded previously but now the expansions list is empty,
# it means adding exclusions has exhausted the list. Remove the section entirely.
if previously_expanded and not self.sections[section].expanded.content:
del self.sections[section]
else:
# A rule section can have multiple rule sections excluded from it. Get the
# most specific rule from the list, and if an even more specific rule is found,
# replace it entirely. Otherwise, keep appending.
exclusions = self.sections[section].excludes
exclusions_list = exclusions.content if exclusions.content != None else []
exclusions_to_remove = filter(lambda r: r.is_more_specific_rule_of(other), exclusions_list)
remaining_exclusions = [e for e in exclusions_list if e not in exclusions_to_remove]
remaining_exclusions.append(other)
self.sections[section].excludes.content = remaining_exclusions
def get_sections_intersection(self, other):
return set(self.sections.keys()).intersection(set(other.sections.keys()))
def is_more_specific_rule_of(self, other):
if (self.specificity <= other.specificity):
return False
# Compare archive, obj and target
for entity_index in range (1, other.specificity + 1):
if self[entity_index] != other[entity_index] and other[entity_index] != None:
return False
return True
def maps_same_entities_as(self, other):
if self.specificity != other.specificity:
return False
# Compare archive, obj and target
for entity_index in range (1, other.specificity + 1):
if self[entity_index] != other[entity_index] and other[entity_index] != None:
return False
return True
def __getitem__(self, key):
if key == PlacementRule.ARCHIVE_SPECIFICITY:
return self.archive
elif key == PlacementRule.OBJECT_SPECIFICITY:
return self.obj
elif key == PlacementRule.SYMBOL_SPECIFICITY:
return self.symbol
else:
return None
def __str__(self):
sorted_sections = sorted(self.get_section_names())
sections_string = list()
for section in sorted_sections:
exclusions = self.sections[section].excludes.content
exclusion_string = None
if exclusions:
exclusion_string = " ".join(map(lambda e: "*" + e.archive + (":" + e.obj + ".*" if e.obj else ""), exclusions))
exclusion_string = "EXCLUDE_FILE(" + exclusion_string + ")"
else:
exclusion_string = ""
section_string = None
exclusion_section_string = None
section_expansions = self.sections[section].expansions.content
section_expanded = self.sections[section].expanded.content
if section_expansions and section_expanded:
section_string = " ".join(section_expansions)
exclusion_section_string = section_string
else:
section_string = section
exclusion_section_string = exclusion_string + " " + section_string
sections_string.append(exclusion_section_string)
sections_string = " ".join(sections_string)
archive = str(self.archive) if self.archive else ""
obj = (str(self.obj) + (".*" if self.obj else "")) if self.obj else ""
# Handle output string generation based on information available
if self.specificity == PlacementRule.DEFAULT_SPECIFICITY:
rule_string = "*(%s)" % (sections_string)
elif self.specificity == PlacementRule.ARCHIVE_SPECIFICITY:
rule_string = "*%s:(%s)" % (archive, sections_string)
else:
rule_string = "*%s:%s(%s)" % (archive, obj, sections_string)
return rule_string
def __eq__(self, other):
if id(self) == id(other):
return True
def exclusions_set(exclusions):
exclusions_set = {(e.archive, e.obj, e.symbol, e.target) for e in exclusions}
return exclusions_set
if self.archive != other.archive:
return False
if self.obj != other.obj:
return False
if self.symbol != other.symbol:
return False
if set(self.sections.keys()) != set(other.sections.keys()):
return False
for (section, metadata) in self.sections.items():
self_meta = metadata
other_meta = other.sections[section]
if exclusions_set(self_meta.excludes.content) != exclusions_set(other_meta.excludes.content):
return False
if set(self_meta.expansions.content) != set(other_meta.expansions.content):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __iter__(self):
yield self.archive
yield self.obj
yield self.symbol
raise StopIteration
"""
Implements generation of placement rules based on collected sections, scheme and mapping fragment.
"""
class GenerationModel:
DEFAULT_SCHEME = "default"
def __init__(self):
self.schemes = {}
self.sections = {}
self.mappings = {}
def _add_mapping_rules(self, archive, obj, symbol, scheme_name, scheme_dict, rules):
# Use an ordinary dictionary to raise exception on non-existing keys
temp_dict = dict(scheme_dict)
sections_bucket = temp_dict[scheme_name]
for (target, sections) in sections_bucket.items():
section_entries = []
for section in sections:
section_entries.extend(section.entries)
rule = PlacementRule(archive, obj, symbol, section_entries, target)
if not rule in rules:
rules.append(rule)
def _build_scheme_dictionary(self):
scheme_dictionary = collections.defaultdict(dict)
# Collect sections into buckets based on target name
for scheme in self.schemes.values():
sections_bucket = collections.defaultdict(list)
for (sections_name, target_name) in scheme.entries:
# Get the sections under the bucket 'target_name'. If this bucket does not exist
# is is created automatically
sections_in_bucket = sections_bucket[target_name]
try:
sections = self.sections[sections_name]
except KeyError:
message = GenerationException.UNDEFINED_REFERENCE + " to sections '" + sections + "'."
raise GenerationException(message, scheme)
sections_in_bucket.append(sections)
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 sections_a, sections_b in itertools.combinations(sections_bucket.values(), 2):
set_a = set()
set_b = set()
for sections in sections_a:
set_a.update(sections.entries)
for sections in sections_b:
set_b.update(sections.entries)
intersection = set_a.intersection(set_b)
# If the intersection is a non-empty set, it means sections are mapped to multiple
# targets. Raise exception.
if intersection:
scheme = self.schemes[scheme_name]
message = "Sections " + str(intersection) + " mapped to multiple targets."
raise GenerationException(message, scheme)
return scheme_dictionary
def generate_rules(self, sdkconfig, sections_infos):
placement_rules = collections.defaultdict(list)
scheme_dictionary = self._build_scheme_dictionary()
# Generate default rules
default_rules = list()
self._add_mapping_rules(None, None, None, GenerationModel.DEFAULT_SCHEME, scheme_dictionary, default_rules)
all_mapping_rules = collections.defaultdict(list)
# Generate rules based on mapping fragments
for mapping in self.mappings.values():
for (condition, entries) in mapping.entries:
condition_true = False
# Only non-default condition are evaluated agains sdkconfig model
if condition != Mapping.DEFAULT_CONDITION:
try:
condition_true = sdkconfig.evaluate_expression(condition)
except Exception as e:
raise GenerationException(e.message, mapping)
else:
condition_true = True
if condition_true:
mapping_rules = list()
archive = mapping.archive
for (obj, symbol, scheme_name) in entries:
try:
self._add_mapping_rules(archive, obj, symbol, scheme_name, scheme_dictionary, mapping_rules)
except KeyError:
message = GenerationException.UNDEFINED_REFERENCE + " to scheme '" + scheme_name + "'."
raise GenerationException(message, mapping)
all_mapping_rules[mapping.name] = mapping_rules
break # Exit on first condition that evaluates to true
# Detect rule conflicts
for mapping_rules in all_mapping_rules.items():
self._detect_conflicts(mapping_rules)
# Add exclusions
for mapping_rules in all_mapping_rules.values():
self._create_exclusions(mapping_rules, default_rules, sections_infos)
# Add the default rules grouped by target
for default_rule in default_rules:
existing_rules = placement_rules[default_rule.target]
if default_rule.get_section_names():
existing_rules.append(default_rule)
for mapping_rules in all_mapping_rules.values():
# Add the mapping rules grouped by target
for mapping_rule in mapping_rules:
existing_rules = placement_rules[mapping_rule.target]
if mapping_rule.get_section_names():
existing_rules.append(mapping_rule)
return placement_rules
def _detect_conflicts(self, rules):
(archive, rules_list) = rules
for specificity in range(0, PlacementRule.OBJECT_SPECIFICITY + 1):
rules_with_specificity = filter(lambda r: r.specificity == specificity, rules_list)
for rule_a, rule_b in itertools.combinations(rules_with_specificity, 2):
intersections = rule_a.get_sections_intersection(rule_b)
if intersections and rule_a.maps_same_entities_as(rule_b):
rules_string = str([str(rule_a), str(rule_b)])
message = "Rules " + rules_string + " map sections " + str(list(intersections)) + " into multiple targets."
mapping = self.mappings[Mapping.get_mapping_name_from_archive(archive)]
raise GenerationException(message, mapping)
def _create_extra_rules(self, rules):
# This function generates extra rules for symbol specific rules. The reason for generating extra rules is to isolate,
# as much as possible, rules that require expansion. Particularly, object specific extra rules are generated.
rules_to_process = sorted(rules, key = lambda r: r.specificity)
symbol_specific_rules = list(filter(lambda r: r.specificity == PlacementRule.SYMBOL_SPECIFICITY, rules_to_process))
extra_rules = dict()
for symbol_specific_rule in symbol_specific_rules:
extra_rule_candidate = {s: None for s in symbol_specific_rule.get_section_names()}
super_rules = filter(lambda r: symbol_specific_rule.is_more_specific_rule_of(r), rules_to_process)
# Take a look at the existing rules that are more general than the current symbol-specific rule.
# Only generate an extra rule if there is no existing object specific rule for that section
for super_rule in super_rules:
intersections = symbol_specific_rule.get_sections_intersection(super_rule)
for intersection in intersections:
if super_rule.specificity != PlacementRule.OBJECT_SPECIFICITY:
extra_rule_candidate[intersection] = super_rule
else:
extra_rule_candidate[intersection] = None
# Generate the extra rules for the symbol specific rule section, keeping track of the generated extra rules
for (section, section_rule) in extra_rule_candidate.items():
if section_rule:
extra_rule = None
extra_rules_key = (symbol_specific_rule.archive, symbol_specific_rule.obj, section_rule.target)
try:
extra_rule = extra_rules[extra_rules_key]
if section not in extra_rule.get_section_names():
new_rule = PlacementRule(extra_rule.archive, extra_rule.obj, extra_rule.symbol, list(extra_rule.get_section_names()) + [section] , extra_rule.target)
extra_rules[extra_rules_key] = new_rule
except KeyError:
extra_rule = PlacementRule(symbol_specific_rule.archive, symbol_specific_rule.obj, None, [section], section_rule.target)
extra_rules[extra_rules_key] = extra_rule
return extra_rules.values()
def _create_exclusions(self, mapping_rules, default_rules, sections_info):
rules = list(default_rules)
rules.extend(mapping_rules)
extra_rules = self._create_extra_rules(rules)
mapping_rules.extend(extra_rules)
rules.extend(extra_rules)
# Sort the rules by means of how specific they are. Sort by specificity from lowest to highest
# * -> lib:* -> lib:obj -> lib:obj:symbol
sorted_rules = sorted(rules, key = lambda r: r.specificity)
# Now that the rules have been sorted, loop through each rule, and then loop
# through rules below it (higher indeces), adding exclusions whenever appropriate.
for general_rule in sorted_rules:
for specific_rule in reversed(sorted_rules):
if (specific_rule.specificity > general_rule.specificity and \
specific_rule.specificity != PlacementRule.SYMBOL_SPECIFICITY) or \
(specific_rule.specificity == PlacementRule.SYMBOL_SPECIFICITY and \
general_rule.specificity == PlacementRule.OBJECT_SPECIFICITY):
general_rule.add_exclusion(specific_rule, sections_info)
def add_fragments_from_file(self, fragment_file):
for fragment in fragment_file.fragments:
dict_to_append_to = None
if isinstance(fragment, Scheme):
dict_to_append_to = self.schemes
elif isinstance(fragment, Sections):
dict_to_append_to = self.sections
else:
dict_to_append_to = self.mappings
# Raise exception when the fragment of the same type is already in the stored fragments
if fragment.name in dict_to_append_to.keys():
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)
raise GenerationException(message)
dict_to_append_to[fragment.name] = fragment
"""
Encapsulates a linker script template file. Finds marker syntax and handles replacement to generate the
final output.
"""
class TemplateModel:
Marker = collections.namedtuple("Marker", "target indent rules")
def __init__(self, template_file):
self.members = []
self.file = os.path.realpath(template_file.name)
self._generate_members(template_file)
def _generate_members(self, template_file):
lines = template_file.readlines()
target = Fragment.IDENTIFIER
reference = Suppress("mapping") + Suppress("[") + target.setResultsName("target") + Suppress("]")
pattern = White(" \t").setResultsName("indent") + reference
# 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:
try:
parsed = pattern.parseString(line)
indent = parsed.indent
target = parsed.target
marker = TemplateModel.Marker(target, indent, [])
self.members.append(marker)
except ParseException:
# Does not match marker syntax
self.members.append(line)
def fill(self, mapping_rules, sdkconfig):
for member in self.members:
target = None
try:
target = member.target
indent = member.indent
rules = member.rules
del rules[:]
rules.extend(mapping_rules[target])
except KeyError:
message = GenerationException.UNDEFINED_REFERENCE + " to target '" + target + "'."
raise GenerationException(message)
except AttributeError as a:
pass
def write(self, output_file):
# 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("\n")
# Do the text replacement
for member in self.members:
try:
indent = member.indent
rules = member.rules
for rule in rules:
generated_line = "".join([indent, str(rule), '\n'])
output_file.write(generated_line)
except AttributeError:
output_file.write(member)
"""
Exception for linker script generation failures such as undefined references/ failure to
evaluate conditions, duplicate mappings, etc.
"""
class GenerationException(Exception):
UNDEFINED_REFERENCE = "Undefined reference"
def __init__(self, message, fragment=None):
self.fragment = fragment
self.message = message
def __str__(self):
if self.fragment:
return "%s\nIn fragment '%s' defined in '%s'." % (self.message, self.fragment.name, self.fragment.path)
else:
return self.message
"""
Encapsulates an output of objdump. Contains information about the static library sections
and names
"""
class SectionsInfo(dict):
PATH = Optional("/") + ZeroOrMore(Regex(r"[^/.]+") + Literal("/"))
__info = collections.namedtuple("__info", "filename content")
def __init__(self):
self.sections = dict()
def add_sections_info(self, sections_info_file):
first_line = sections_info_file.readline()
archive = Literal("In archive").suppress() + SectionsInfo.PATH.suppress() + Fragment.ENTITY.setResultsName("archive") + Literal(":").suppress()
parser = archive
results = None
try:
results = parser.parseString(first_line)
except ParseException as p:
raise ParseException("File " + sections_info_file.name + " is not a valid sections info file. " + p.message)
self.sections[results.archive] = SectionsInfo.__info(sections_info_file.name, sections_info_file.read())
def _get_infos_from_file(self, info):
# Object file line: '{object}: file format elf32-xtensa-le'
object = Fragment.ENTITY.setResultsName("object") + Literal(":").suppress() + Literal("file format elf32-xtensa-le").suppress()
# Sections table
header = Suppress(Literal("Sections:") + Literal("Idx") + Literal("Name") + Literal("Size") + Literal("VMA") + Literal("LMA") + Literal("File off") + Literal("Algn"))
entry = Word(nums).suppress() + Fragment.ENTITY + Suppress(OneOrMore(Word(alphanums, exact=8)) + Word(nums + "*") + ZeroOrMore(Word(alphas.upper()) + Optional(Literal(","))))
# Content is object file line + sections table
content = Group(object + header + Group(ZeroOrMore(entry)).setResultsName("sections"))
parser = Group(ZeroOrMore(content)).setResultsName("contents")
sections_info_text = info.content
results = None
try:
results = parser.parseString(sections_info_text)
except ParseException as p:
raise ParseException("Unable to parse section info file " + info.filename + ". " + p.message)
return results
def get_obj_sections(self, archive, obj):
stored = self.sections[archive]
# Parse the contents of the sections file
if not isinstance(stored, dict):
parsed = self._get_infos_from_file(stored)
stored = dict()
for content in parsed.contents:
sections = list(map(lambda s: s, content.sections))
stored[content.object] = sections
self.sections[archive] = stored
for obj_key in stored.keys():
if obj_key == obj + ".o" or obj_key == obj + ".c.obj":
return stored[obj_key]

101
tools/ldgen/ldgen.py Executable file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
#
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import os
from fragments import FragmentFileModel
from sdkconfig import SDKConfig
from generation import GenerationModel, TemplateModel, SectionsInfo
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(
"--fragments", "-f",
type = argparse.FileType("r"),
help = "Input fragment files",
nargs = "+")
argparser.add_argument(
"--sections", "-s",
type = argparse.FileType("r"),
help = "Library sections info",
nargs = "+")
argparser.add_argument(
"--output", "-o",
help = "Output linker script",
type = argparse.FileType("w"))
argparser.add_argument(
"--config", "-c",
help = "Project configuration",
type = argparse.FileType("r"))
argparser.add_argument(
"--kconfig", "-k",
help = "IDF Kconfig file",
type = argparse.FileType("r"))
argparser.add_argument(
"--env", "-e",
action='append', default=[],
help='Environment to set when evaluating the config file', metavar='NAME=VAL')
args = argparser.parse_args()
input_file = args.input
fragment_files = [] if not args.fragments else args.fragments
config_file = args.config
output_file = args.output
sections_info_files = [] if not args.sections else args.sections
kconfig_file = args.kconfig
try:
sections_infos = SectionsInfo()
for sections_info_file in sections_info_files:
sections_infos.add_sections_info(sections_info_file)
generation_model = GenerationModel()
for fragment_file in fragment_files:
fragment_file = FragmentFileModel(fragment_file)
generation_model.add_fragments_from_file(fragment_file)
sdkconfig = SDKConfig(kconfig_file, config_file, args.env)
mapping_rules = generation_model.generate_rules(sdkconfig, sections_infos)
script_model = TemplateModel(input_file)
script_model.fill(mapping_rules, sdkconfig)
script_model.write(output_file)
except Exception, e:
print("linker script generation failed for %s\nERROR: %s" % (input_file.name, e.message))
# Delete the file so the entire build will fail; and not use an outdated script.
os.remove(output_file.name)
if __name__ == "__main__":
main()

5720
tools/ldgen/pyparsing.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
[sections:text]
entries:
.text+
.literal+
[sections:data]
entries:
.data+
[sections:bss]
entries:
.bss+
[sections:common]
entries:
COMMON
[sections:rodata]
entries:
.rodata+
[sections:rtc_text]
entries:
.rtc.text
.rtc.literal
[sections:rtc_data]
entries:
.rtc.data
[sections:rtc_rodata]
entries:
.rtc.rodata
[sections:rtc_bss]
entries:
.rtc.bss
[sections:extram_bss]
entries:
.exram.bss
[sections:iram]
entries:
.iram1+
[sections:dram]
entries:
.dram1+
[scheme:default]
entries:
text -> flash_text
rodata -> flash_rodata
data -> dram0_data
bss -> dram0_bss
common -> dram0_bss
iram -> iram0_text
dram -> dram0_data
rtc_text -> rtc_text
rtc_data -> rtc_data
rtc_rodata -> rtc_data
rtc_bss -> rtc_bss
[scheme:rtc]
entries:
text -> rtc_text
data -> rtc_data
rodata -> rtc_data
bss -> rtc_bss
common -> rtc_bss
[scheme:noflash]
entries:
text -> iram0_text
rodata -> dram0_data
[scheme:noflash_data]
entries:
rodata -> dram0_data

View File

@ -0,0 +1,62 @@
[mapping]
archive: libheap.a
entries:
multi_heap (noflash)
multi_heap_poisoning (noflash)
[mapping]
archive: libsoc.a
entries:
* (noflash)
[mapping]
archive: libfreertos.a
entries:
* (noflash)
[mapping]
archive: libesp32.a
entries:
core_dump (noflash)
panic (noflash)
[mapping]
archive: libapp_trace.a
entries:
* (noflash)
[mapping]
archive: libxtensa-debug-module.a
entries:
eri (noflash)
[mapping]
archive: libphy.a
entries:
* (noflash_data)
[mapping]
archive: librtc.a
entries:
* (noflash)
[mapping]
archive: libhal.a
entries:
* (noflash)
[mapping]
archive: libgcc.a
entries:
lib2funcs (noflash)
[mapping]
archive: libspi_flash.a
entries:
spi_flash_rom_patch (noflash)
[mapping]
archive: libgcov.a
entries:
* (noflash)

View File

@ -0,0 +1,542 @@
#
# SDK tool configuration
#
CONFIG_TOOLPREFIX="xtensa-esp32-elf-"
CONFIG_PYTHON="python"
CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=y
#
# Bootloader config
#
CONFIG_LOG_BOOTLOADER_LEVEL_NONE=
CONFIG_LOG_BOOTLOADER_LEVEL_ERROR=
CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
CONFIG_LOG_BOOTLOADER_LEVEL_INFO=
CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG=
CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE=
CONFIG_LOG_BOOTLOADER_LEVEL=2
CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V=
CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y
#
# Security features
#
CONFIG_SECURE_BOOT_ENABLED=
CONFIG_FLASH_ENCRYPTION_ENABLED=
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0"
CONFIG_ESPTOOLPY_BAUD_115200B=y
CONFIG_ESPTOOLPY_BAUD_230400B=
CONFIG_ESPTOOLPY_BAUD_921600B=
CONFIG_ESPTOOLPY_BAUD_2MB=
CONFIG_ESPTOOLPY_BAUD_OTHER=
CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200
CONFIG_ESPTOOLPY_BAUD=115200
CONFIG_ESPTOOLPY_COMPRESSED=
CONFIG_FLASHMODE_QIO=
CONFIG_FLASHMODE_QOUT=
CONFIG_FLASHMODE_DIO=y
CONFIG_FLASHMODE_DOUT=
CONFIG_ESPTOOLPY_FLASHMODE="dio"
CONFIG_ESPTOOLPY_FLASHFREQ_80M=
CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
CONFIG_ESPTOOLPY_FLASHFREQ_26M=
CONFIG_ESPTOOLPY_FLASHFREQ_20M=
CONFIG_ESPTOOLPY_FLASHFREQ="40m"
CONFIG_ESPTOOLPY_FLASHSIZE_1MB=
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
CONFIG_ESPTOOLPY_BEFORE_RESET=y
CONFIG_ESPTOOLPY_BEFORE_NORESET=
CONFIG_ESPTOOLPY_BEFORE="default_reset"
CONFIG_ESPTOOLPY_AFTER_RESET=y
CONFIG_ESPTOOLPY_AFTER_NORESET=
CONFIG_ESPTOOLPY_AFTER="hard_reset"
CONFIG_MONITOR_BAUD_9600B=
CONFIG_MONITOR_BAUD_57600B=
CONFIG_MONITOR_BAUD_115200B=y
CONFIG_MONITOR_BAUD_230400B=
CONFIG_MONITOR_BAUD_921600B=
CONFIG_MONITOR_BAUD_2MB=
CONFIG_MONITOR_BAUD_OTHER=
CONFIG_MONITOR_BAUD_OTHER_VAL=115200
CONFIG_MONITOR_BAUD=115200
#
# Partition Table
#
CONFIG_PARTITION_TABLE_SINGLE_APP=y
CONFIG_PARTITION_TABLE_TWO_OTA=
CONFIG_PARTITION_TABLE_CUSTOM=
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
CONFIG_APP_OFFSET=0x10000
CONFIG_PARTITION_TABLE_MD5=y
#
# Compiler options
#
CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
CONFIG_OPTIMIZATION_LEVEL_RELEASE=
CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
CONFIG_OPTIMIZATION_ASSERTIONS_SILENT=
CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED=
CONFIG_CXX_EXCEPTIONS=
CONFIG_STACK_CHECK_NONE=y
CONFIG_STACK_CHECK_NORM=
CONFIG_STACK_CHECK_STRONG=
CONFIG_STACK_CHECK_ALL=
CONFIG_STACK_CHECK=
CONFIG_WARN_WRITE_STRINGS=
#
# Component config
#
#
# Application Level Tracing
#
CONFIG_ESP32_APPTRACE_DEST_TRAX=
CONFIG_ESP32_APPTRACE_DEST_NONE=y
CONFIG_ESP32_APPTRACE_ENABLE=
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
#
# FreeRTOS SystemView Tracing
#
CONFIG_AWS_IOT_SDK=
#
# Bluetooth
#
CONFIG_BT_ENABLED=
CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0
CONFIG_BT_RESERVE_DRAM=0
#
# ADC configuration
#
CONFIG_ADC_FORCE_XPD_FSM=
CONFIG_ADC2_DISABLE_DAC=y
#
# ESP32-specific
#
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=
CONFIG_ESP32_DEFAULT_CPU_FREQ_160=
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_SPIRAM_SUPPORT=
CONFIG_MEMMAP_TRACEMEM=
CONFIG_MEMMAP_TRACEMEM_TWOBANKS=
CONFIG_ESP32_TRAX=
CONFIG_TRACEMEM_RESERVE_DRAM=0x0
CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH=
CONFIG_ESP32_ENABLE_COREDUMP_TO_UART=
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
CONFIG_ESP32_ENABLE_COREDUMP=
CONFIG_TWO_UNIVERSAL_MAC_ADDRESS=
CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y
CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2048
CONFIG_MAIN_TASK_STACK_SIZE=4096
CONFIG_IPC_TASK_STACK_SIZE=1024
CONFIG_TIMER_TASK_STACK_SIZE=3584
CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF=
CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR=
CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF=
CONFIG_NEWLIB_STDIN_LINE_ENDING_LF=
CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y
CONFIG_NEWLIB_NANO_FORMAT=
CONFIG_CONSOLE_UART_DEFAULT=y
CONFIG_CONSOLE_UART_CUSTOM=
CONFIG_CONSOLE_UART_NONE=
CONFIG_CONSOLE_UART_NUM=0
CONFIG_CONSOLE_UART_BAUDRATE=115200
CONFIG_ULP_COPROC_ENABLED=
CONFIG_ULP_COPROC_RESERVE_MEM=0
CONFIG_ESP32_PANIC_PRINT_HALT=
CONFIG_ESP32_PANIC_PRINT_REBOOT=y
CONFIG_ESP32_PANIC_SILENT_REBOOT=
CONFIG_ESP32_PANIC_GDBSTUB=
CONFIG_ESP32_DEBUG_OCDAWARE=y
CONFIG_INT_WDT=y
CONFIG_INT_WDT_TIMEOUT_MS=300
CONFIG_TASK_WDT=y
CONFIG_TASK_WDT_PANIC=
CONFIG_TASK_WDT_TIMEOUT_S=5
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
CONFIG_BROWNOUT_DET=y
CONFIG_BROWNOUT_DET_LVL_SEL_0=y
CONFIG_BROWNOUT_DET_LVL_SEL_1=
CONFIG_BROWNOUT_DET_LVL_SEL_2=
CONFIG_BROWNOUT_DET_LVL_SEL_3=
CONFIG_BROWNOUT_DET_LVL_SEL_4=
CONFIG_BROWNOUT_DET_LVL_SEL_5=
CONFIG_BROWNOUT_DET_LVL_SEL_6=
CONFIG_BROWNOUT_DET_LVL_SEL_7=
CONFIG_BROWNOUT_DET_LVL=0
CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
CONFIG_ESP32_TIME_SYSCALL_USE_RTC=
CONFIG_ESP32_TIME_SYSCALL_USE_FRC1=
CONFIG_ESP32_TIME_SYSCALL_USE_NONE=
CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y
CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL=
CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024
CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES=100
CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000
CONFIG_ESP32_XTAL_FREQ_40=y
CONFIG_ESP32_XTAL_FREQ_26=
CONFIG_ESP32_XTAL_FREQ_AUTO=
CONFIG_ESP32_XTAL_FREQ=40
CONFIG_DISABLE_BASIC_ROM_CONSOLE=
CONFIG_NO_BLOBS=
CONFIG_ESP_TIMER_PROFILING=
CONFIG_COMPATIBLE_PRE_V2_1_BOOTLOADERS=
CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
#
# Wi-Fi
#
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP32_WIFI_TX_BA_WIN=6
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_ESP32_WIFI_NVS_ENABLED=y
#
# PHY
#
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION=
CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20
#
# Power Management
#
CONFIG_PM_ENABLE=
#
# ADC-Calibration
#
CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y
CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y
CONFIG_ADC_CAL_LUT_ENABLE=y
#
# Ethernet
#
CONFIG_DMA_RX_BUF_NUM=10
CONFIG_DMA_TX_BUF_NUM=10
CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE=
CONFIG_EMAC_TASK_PRIORITY=20
#
# FAT Filesystem support
#
CONFIG_FATFS_CODEPAGE_DYNAMIC=
CONFIG_FATFS_CODEPAGE_437=y
CONFIG_FATFS_CODEPAGE_720=
CONFIG_FATFS_CODEPAGE_737=
CONFIG_FATFS_CODEPAGE_771=
CONFIG_FATFS_CODEPAGE_775=
CONFIG_FATFS_CODEPAGE_850=
CONFIG_FATFS_CODEPAGE_852=
CONFIG_FATFS_CODEPAGE_855=
CONFIG_FATFS_CODEPAGE_857=
CONFIG_FATFS_CODEPAGE_860=
CONFIG_FATFS_CODEPAGE_861=
CONFIG_FATFS_CODEPAGE_862=
CONFIG_FATFS_CODEPAGE_863=
CONFIG_FATFS_CODEPAGE_864=
CONFIG_FATFS_CODEPAGE_865=
CONFIG_FATFS_CODEPAGE_866=
CONFIG_FATFS_CODEPAGE_869=
CONFIG_FATFS_CODEPAGE_932=
CONFIG_FATFS_CODEPAGE_936=
CONFIG_FATFS_CODEPAGE_949=
CONFIG_FATFS_CODEPAGE_950=
CONFIG_FATFS_CODEPAGE=437
CONFIG_FATFS_LFN_NONE=y
CONFIG_FATFS_LFN_HEAP=
CONFIG_FATFS_LFN_STACK=
CONFIG_FATFS_FS_LOCK=0
CONFIG_FATFS_TIMEOUT_MS=10000
CONFIG_FATFS_PER_FILE_CACHE=y
#
# FreeRTOS
#
CONFIG_FREERTOS_UNICORE=y
CONFIG_FREERTOS_CORETIMER_0=y
CONFIG_FREERTOS_CORETIMER_1=
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE=
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3
CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y
CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE=
CONFIG_FREERTOS_ASSERT_DISABLE=
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024
CONFIG_FREERTOS_ISR_STACKSIZE=1536
CONFIG_FREERTOS_LEGACY_HOOKS=
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
CONFIG_SUPPORT_STATIC_ALLOCATION=
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
CONFIG_FREERTOS_USE_TRACE_FACILITY=
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=
CONFIG_FREERTOS_DEBUG_INTERNALS=
#
# Heap memory debugging
#
CONFIG_HEAP_POISONING_DISABLED=y
CONFIG_HEAP_POISONING_LIGHT=
CONFIG_HEAP_POISONING_COMPREHENSIVE=
CONFIG_HEAP_TRACING=
#
# libsodium
#
CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y
#
# Log output
#
CONFIG_LOG_DEFAULT_LEVEL_NONE=
CONFIG_LOG_DEFAULT_LEVEL_ERROR=
CONFIG_LOG_DEFAULT_LEVEL_WARN=
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=
CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_COLORS=y
#
# LWIP
#
CONFIG_L2_TO_L3_COPY=
CONFIG_LWIP_IRAM_OPTIMIZATION=
CONFIG_LWIP_MAX_SOCKETS=4
CONFIG_LWIP_SO_REUSE=
CONFIG_LWIP_SO_RCVBUF=
CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1
CONFIG_LWIP_IP_FRAG=
CONFIG_LWIP_IP_REASSEMBLY=
CONFIG_LWIP_STATS=
CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y
CONFIG_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
#
# DHCP server
#
CONFIG_LWIP_DHCPS_LEASE_UNIT=60
CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8
CONFIG_LWIP_AUTOIP=
CONFIG_LWIP_NETIF_LOOPBACK=y
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
#
# TCP
#
CONFIG_LWIP_MAX_ACTIVE_TCP=16
CONFIG_LWIP_MAX_LISTENING_TCP=16
CONFIG_TCP_MAXRTX=12
CONFIG_TCP_SYNMAXRTX=6
CONFIG_TCP_MSS=1436
CONFIG_TCP_MSL=60000
CONFIG_TCP_SND_BUF_DEFAULT=5744
CONFIG_TCP_WND_DEFAULT=5744
CONFIG_TCP_RECVMBOX_SIZE=6
CONFIG_TCP_QUEUE_OOSEQ=y
CONFIG_TCP_OVERSIZE_MSS=y
CONFIG_TCP_OVERSIZE_QUARTER_MSS=
CONFIG_TCP_OVERSIZE_DISABLE=
#
# UDP
#
CONFIG_LWIP_MAX_UDP_PCBS=16
CONFIG_UDP_RECVMBOX_SIZE=6
CONFIG_TCPIP_TASK_STACK_SIZE=2048
CONFIG_PPP_SUPPORT=
#
# ICMP
#
CONFIG_LWIP_MULTICAST_PING=
CONFIG_LWIP_BROADCAST_PING=
#
# LWIP RAW API
#
CONFIG_LWIP_MAX_RAW_PCBS=16
#
# mbedTLS
#
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384
CONFIG_MBEDTLS_DEBUG=
CONFIG_MBEDTLS_HARDWARE_AES=y
CONFIG_MBEDTLS_HARDWARE_MPI=y
CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y
CONFIG_MBEDTLS_HARDWARE_SHA=
CONFIG_MBEDTLS_HAVE_TIME=y
CONFIG_MBEDTLS_HAVE_TIME_DATE=
CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y
CONFIG_MBEDTLS_TLS_SERVER_ONLY=
CONFIG_MBEDTLS_TLS_CLIENT_ONLY=
CONFIG_MBEDTLS_TLS_DISABLED=
CONFIG_MBEDTLS_TLS_SERVER=y
CONFIG_MBEDTLS_TLS_CLIENT=y
CONFIG_MBEDTLS_TLS_ENABLED=y
#
# TLS Key Exchange Methods
#
CONFIG_MBEDTLS_PSK_MODES=
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y
CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y
CONFIG_MBEDTLS_SSL_RENEGOTIATION=y
CONFIG_MBEDTLS_SSL_PROTO_SSL3=
CONFIG_MBEDTLS_SSL_PROTO_TLS1=y
CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y
CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y
CONFIG_MBEDTLS_SSL_PROTO_DTLS=
CONFIG_MBEDTLS_SSL_ALPN=y
CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y
#
# Symmetric Ciphers
#
CONFIG_MBEDTLS_AES_C=y
CONFIG_MBEDTLS_CAMELLIA_C=
CONFIG_MBEDTLS_DES_C=
CONFIG_MBEDTLS_RC4_DISABLED=y
CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT=
CONFIG_MBEDTLS_RC4_ENABLED=
CONFIG_MBEDTLS_BLOWFISH_C=
CONFIG_MBEDTLS_XTEA_C=
CONFIG_MBEDTLS_CCM_C=y
CONFIG_MBEDTLS_GCM_C=y
CONFIG_MBEDTLS_RIPEMD160_C=
#
# Certificates
#
CONFIG_MBEDTLS_PEM_PARSE_C=y
CONFIG_MBEDTLS_PEM_WRITE_C=y
CONFIG_MBEDTLS_X509_CRL_PARSE_C=y
CONFIG_MBEDTLS_X509_CSR_PARSE_C=y
CONFIG_MBEDTLS_ECP_C=y
CONFIG_MBEDTLS_ECDH_C=y
CONFIG_MBEDTLS_ECDSA_C=y
CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y
CONFIG_MBEDTLS_ECP_NIST_OPTIM=y
#
# OpenSSL
#
CONFIG_OPENSSL_DEBUG=
CONFIG_OPENSSL_ASSERT_DO_NOTHING=y
CONFIG_OPENSSL_ASSERT_EXIT=
#
# PThreads
#
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5
CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
#
# SPI Flash driver
#
CONFIG_SPI_FLASH_VERIFY_WRITE=
CONFIG_SPI_FLASH_ENABLE_COUNTERS=
CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y
CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS=
CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED=
#
# SPIFFS Configuration
#
CONFIG_SPIFFS_MAX_PARTITIONS=3
#
# SPIFFS Cache Configuration
#
CONFIG_SPIFFS_CACHE=y
CONFIG_SPIFFS_CACHE_WR=y
CONFIG_SPIFFS_CACHE_STATS=
CONFIG_SPIFFS_PAGE_CHECK=y
CONFIG_SPIFFS_GC_MAX_RUNS=10
CONFIG_SPIFFS_GC_STATS=
CONFIG_SPIFFS_PAGE_SIZE=256
CONFIG_SPIFFS_OBJ_NAME_LEN=32
CONFIG_SPIFFS_USE_MAGIC=y
CONFIG_SPIFFS_USE_MAGIC_LENGTH=y
CONFIG_SPIFFS_META_LENGTH=4
CONFIG_SPIFFS_USE_MTIME=y
#
# Debug Configuration
#
CONFIG_SPIFFS_DBG=
CONFIG_SPIFFS_API_DBG=
CONFIG_SPIFFS_GC_DBG=
CONFIG_SPIFFS_CACHE_DBG=
CONFIG_SPIFFS_CHECK_DBG=
CONFIG_SPIFFS_TEST_VISUALISATION=
#
# tcpip adapter
#
CONFIG_IP_LOST_TIMER_INTERVAL=120
#
# Wear Levelling
#
CONFIG_WL_SECTOR_SIZE_512=
CONFIG_WL_SECTOR_SIZE_4096=y
CONFIG_WL_SECTOR_SIZE=4096

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,217 @@
/* Default entry point: */
ENTRY(call_start_cpu0);
SECTIONS
{
/* RTC fast memory holds RTC wake stub code,
including from any source file named rtc_wake_stub*.c
*/
.rtc.text :
{
. = ALIGN(4);
mapping[rtc_text]
*rtc_wake_stub*.o(.literal .text .literal.* .text.*)
} >rtc_iram_seg
/* RTC slow memory holds RTC wake stub
data/rodata, including from any source file
named rtc_wake_stub*.c
*/
.rtc.data :
{
_rtc_data_start = ABSOLUTE(.);
mapping[rtc_data]
*rtc_wake_stub*.o(.data .rodata .data.* .rodata.* .bss .bss.*)
_rtc_data_end = ABSOLUTE(.);
} > rtc_slow_seg
/* RTC bss, from any source file named rtc_wake_stub*.c */
.rtc.bss (NOLOAD) :
{
_rtc_bss_start = ABSOLUTE(.);
mapping[rtc_bss]
*rtc_wake_stub*.o(.bss .bss.*)
*rtc_wake_stub*.o(COMMON)
_rtc_bss_end = ABSOLUTE(.);
} > rtc_slow_seg
/* Send .iram0 code to iram */
.iram0.vectors :
{
/* Vectors go to IRAM */
_init_start = ABSOLUTE(.);
/* Vectors according to builds/RF-2015.2-win32/esp108_v1_2_s5_512int_2/config.html */
. = 0x0;
KEEP(*(.WindowVectors.text));
. = 0x180;
KEEP(*(.Level2InterruptVector.text));
. = 0x1c0;
KEEP(*(.Level3InterruptVector.text));
. = 0x200;
KEEP(*(.Level4InterruptVector.text));
. = 0x240;
KEEP(*(.Level5InterruptVector.text));
. = 0x280;
KEEP(*(.DebugExceptionVector.text));
. = 0x2c0;
KEEP(*(.NMIExceptionVector.text));
. = 0x300;
KEEP(*(.KernelExceptionVector.text));
. = 0x340;
KEEP(*(.UserExceptionVector.text));
. = 0x3C0;
KEEP(*(.DoubleExceptionVector.text));
. = 0x400;
*(.*Vector.literal)
*(.UserEnter.literal);
*(.UserEnter.text);
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
_init_end = ABSOLUTE(.);
/* This goes here, not at top of linker script, so addr2line finds it last,
and uses it in preference to the first symbol in IRAM */
_iram_start = ABSOLUTE(0);
} > iram0_0_seg
.iram0.text :
{
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
mapping[iram0_text]
INCLUDE esp32.spiram.rom-functions-iram.ld
_iram_text_end = ABSOLUTE(.);
} > iram0_0_seg
.dram0.data :
{
_data_start = ABSOLUTE(.);
mapping[dram0_data]
*(.gnu.linkonce.d.*)
*(.data1)
*(.sdata)
*(.sdata.*)
*(.gnu.linkonce.s.*)
*(.sdata2)
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
*(.jcr)
INCLUDE esp32.spiram.rom-functions-dram.ld
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} >dram0_0_seg
/* Shared RAM */
.dram0.bss (NOLOAD) :
{
. = ALIGN (8);
_bss_start = ABSOLUTE(.);
mapping[dram0_bss]
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.share.mem)
*(.gnu.linkonce.b.*)
. = ALIGN (8);
_bss_end = ABSOLUTE(.);
_heap_start = ABSOLUTE(.);
} >dram0_0_seg
.flash.rodata :
{
_rodata_start = ABSOLUTE(.);
mapping[flash_rodata]
*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
*(.gnu.linkonce.r.*)
*(.rodata1)
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
*(.xt_except_table)
*(.gcc_except_table .gcc_except_table.*)
*(.gnu.linkonce.e.*)
*(.gnu.version_r)
. = (. + 3) & ~ 3;
__eh_frame = ABSOLUTE(.);
KEEP(*(.eh_frame))
. = (. + 7) & ~ 3;
/* C++ constructor and destructor tables, properly ordered: */
__init_array_start = ABSOLUTE(.);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__init_array_end = ABSOLUTE(.);
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
/* C++ exception handlers table: */
__XT_EXCEPTION_DESCS_ = ABSOLUTE(.);
*(.xt_except_desc)
*(.gnu.linkonce.h.*)
__XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.);
*(.xt_except_desc_end)
*(.dynamic)
*(.gnu.version_d)
_rodata_end = ABSOLUTE(.);
/* Literals are also RO data. */
_lit4_start = ABSOLUTE(.);
*(*.lit4)
*(.lit4.*)
*(.gnu.linkonce.lit4.*)
_lit4_end = ABSOLUTE(.);
. = ALIGN(4);
_thread_local_start = ABSOLUTE(.);
*(.tdata)
*(.tdata.*)
*(.tbss)
*(.tbss.*)
_thread_local_end = ABSOLUTE(.);
. = ALIGN(4);
} >drom0_0_seg
.flash.text :
{
_stext = .;
_text_start = ABSOLUTE(.);
mapping[flash_text]
*(.stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
/* Similar to _iram_start, this symbol goes here so it is
resolved by addr2line in preference to the first symbol in
the flash.text segment.
*/
_flash_cache_start = ABSOLUTE(0);
} >iram0_2_seg
}

87
tools/ldgen/sdkconfig.py Normal file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python
#
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
from pyparsing import *
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
kconfig_new_dir = os.path.abspath(parent_dir_name + "/kconfig_new")
sys.path.append(kconfig_new_dir)
import kconfiglib
"""
Encapsulates an sdkconfig file. Defines grammar of a configuration entry, and enables
evaluation of logical expressions involving those entries.
"""
class SDKConfig:
# A configuration entry is in the form CONFIG=VALUE. Definitions of components of that grammar
IDENTIFIER = Word(printables.upper())
HEX = Combine("0x" + Word(hexnums)).setParseAction(lambda t:int(t[0], 16))
DECIMAL = Combine(Optional(Literal("+") | Literal("-")) + Word(nums)).setParseAction(lambda t:int(t[0]))
LITERAL = Word(printables)
QUOTED_LITERAL = quotedString.setParseAction(removeQuotes)
VALUE = HEX | DECIMAL | LITERAL | QUOTED_LITERAL
# Operators supported by the expression evaluation
OPERATOR = oneOf(["=", "!=", ">", "<", "<=", ">="])
def __init__(self, kconfig_file, sdkconfig_file, env = []):
env = [ (name, value) for (name,value) in ( e.split("=",1) for e in env) ]
for name, value in env:
value = " ".join(value.split())
os.environ[name] = value
self.config = kconfiglib.Kconfig(kconfig_file.name)
self.config.load_config(sdkconfig_file.name)
def evaluate_expression(self, expression):
result = self.config.eval_string(expression)
if result == 0: # n
return False
elif result == 2: # y
return True
else: # m
raise Exception("Unsupported config expression result.")
@staticmethod
def get_expression_grammar():
identifier = SDKConfig.IDENTIFIER.setResultsName("identifier")
operator = SDKConfig.OPERATOR.setResultsName("operator")
value = SDKConfig.VALUE.setResultsName("value")
test_binary = identifier + operator + value
test_single = identifier
test = test_binary | test_single
condition = Group(Optional("(").suppress() + test + Optional(")").suppress())
grammar = infixNotation(
condition, [
("!", 1, opAssoc.RIGHT),
("&&", 2, opAssoc.LEFT),
("||", 2, opAssoc.LEFT)])
return grammar

View File

@ -0,0 +1,7 @@
menu "Test config"
config PERFORMANCE_LEVEL
int "Performance level"
range 0 3
endmenu

View File

@ -0,0 +1,84 @@
[sections:text]
entries:
.text+
.literal+
[sections:data]
entries:
.data+
[sections:bss]
entries:
.bss+
[sections:common]
entries:
COMMON
[sections:rodata]
entries:
.rodata+
[sections:rtc_text]
entries:
.rtc.text
.rtc.literal
[sections:rtc_data]
entries:
.rtc.data
[sections:rtc_rodata]
entries:
.rtc.rodata
[sections:rtc_bss]
entries:
.rtc.bss
[sections:extram_bss]
entries:
.exram.bss
[sections:iram]
entries:
.iram+
[sections:dram]
entries:
.dram+
[scheme:default]
entries:
text -> flash_text
rodata -> flash_rodata
data -> dram0_data
bss -> dram0_bss
common -> dram0_bss
iram -> iram0_text
dram -> dram0_data
rtc_text -> rtc_text
rtc_data -> rtc_data
rtc_rodata -> rtc_data
rtc_bss -> rtc_bss
[scheme:rtc]
entries:
text -> rtc_text
data -> rtc_data
rodata -> rtc_data
bss -> rtc_bss
common -> rtc_bss
[scheme:noflash]
entries:
text -> iram0_text
rodata -> dram0_data
[scheme:noflash_text]
entries:
text -> iram0_text
[scheme:noflash_data]
entries:
rodata -> dram0_data

View File

@ -0,0 +1,8 @@
CONFIG_TEST_STRING="This Is~AString#$^#$&^(*&^#(*&^)(*@_)(#*_)(*_(*}Value"
CONFIG_TEST_NON_STRING=y
CONFIG_TEST_WHITESPACE= y
CONFIG_TEST_EMPTY=
CONFIG_TEST_POSITIVE_INT=110
CONFIG_TEST_HEX_INT=0x8000
CONFIG_TEST_NEGATIVE_INT=-9
CONFIG_PERFORMANCE_LEVEL=0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,217 @@
/* Default entry point: */
ENTRY(call_start_cpu0);
SECTIONS
{
/* RTC fast memory holds RTC wake stub code,
including from any source file named rtc_wake_stub*.c
*/
.rtc.text :
{
. = ALIGN(4);
mapping[rtc_text]
*rtc_wake_stub*.o(.literal .text .literal.* .text.*)
} >rtc_iram_seg
/* RTC slow memory holds RTC wake stub
data/rodata, including from any source file
named rtc_wake_stub*.c
*/
.rtc.data :
{
_rtc_data_start = ABSOLUTE(.);
mapping[rtc_data]
*rtc_wake_stub*.o(.data .rodata .data.* .rodata.* .bss .bss.*)
_rtc_data_end = ABSOLUTE(.);
} > rtc_slow_seg
/* RTC bss, from any source file named rtc_wake_stub*.c */
.rtc.bss (NOLOAD) :
{
_rtc_bss_start = ABSOLUTE(.);
mapping[rtc_bss]
*rtc_wake_stub*.o(.bss .bss.*)
*rtc_wake_stub*.o(COMMON)
_rtc_bss_end = ABSOLUTE(.);
} > rtc_slow_seg
/* Send .iram0 code to iram */
.iram0.vectors :
{
/* Vectors go to IRAM */
_init_start = ABSOLUTE(.);
/* Vectors according to builds/RF-2015.2-win32/esp108_v1_2_s5_512int_2/config.html */
. = 0x0;
KEEP(*(.WindowVectors.text));
. = 0x180;
KEEP(*(.Level2InterruptVector.text));
. = 0x1c0;
KEEP(*(.Level3InterruptVector.text));
. = 0x200;
KEEP(*(.Level4InterruptVector.text));
. = 0x240;
KEEP(*(.Level5InterruptVector.text));
. = 0x280;
KEEP(*(.DebugExceptionVector.text));
. = 0x2c0;
KEEP(*(.NMIExceptionVector.text));
. = 0x300;
KEEP(*(.KernelExceptionVector.text));
. = 0x340;
KEEP(*(.UserExceptionVector.text));
. = 0x3C0;
KEEP(*(.DoubleExceptionVector.text));
. = 0x400;
*(.*Vector.literal)
*(.UserEnter.literal);
*(.UserEnter.text);
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
_init_end = ABSOLUTE(.);
/* This goes here, not at top of linker script, so addr2line finds it last,
and uses it in preference to the first symbol in IRAM */
_iram_start = ABSOLUTE(0);
} > iram0_0_seg
.iram0.text :
{
/* Code marked as runnning out of IRAM */
_iram_text_start = ABSOLUTE(.);
mapping[iram0_text]
INCLUDE esp32.spiram.rom-functions-iram.ld
_iram_text_end = ABSOLUTE(.);
} > iram0_0_seg
.dram0.data :
{
_data_start = ABSOLUTE(.);
mapping[dram0_data]
*(.gnu.linkonce.d.*)
*(.data1)
*(.sdata)
*(.sdata.*)
*(.gnu.linkonce.s.*)
*(.sdata2)
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
*(.jcr)
INCLUDE esp32.spiram.rom-functions-dram.ld
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} >dram0_0_seg
/* Shared RAM */
.dram0.bss (NOLOAD) :
{
. = ALIGN (8);
_bss_start = ABSOLUTE(.);
mapping[dram0_bss]
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
*(.scommon)
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
*(.dynbss)
*(.share.mem)
*(.gnu.linkonce.b.*)
. = ALIGN (8);
_bss_end = ABSOLUTE(.);
_heap_start = ABSOLUTE(.);
} >dram0_0_seg
.flash.rodata :
{
_rodata_start = ABSOLUTE(.);
mapping[flash_rodata]
*(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
*(.gnu.linkonce.r.*)
*(.rodata1)
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
*(.xt_except_table)
*(.gcc_except_table .gcc_except_table.*)
*(.gnu.linkonce.e.*)
*(.gnu.version_r)
. = (. + 3) & ~ 3;
__eh_frame = ABSOLUTE(.);
KEEP(*(.eh_frame))
. = (. + 7) & ~ 3;
/* C++ constructor and destructor tables, properly ordered: */
__init_array_start = ABSOLUTE(.);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__init_array_end = ABSOLUTE(.);
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
/* C++ exception handlers table: */
__XT_EXCEPTION_DESCS_ = ABSOLUTE(.);
*(.xt_except_desc)
*(.gnu.linkonce.h.*)
__XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.);
*(.xt_except_desc_end)
*(.dynamic)
*(.gnu.version_d)
_rodata_end = ABSOLUTE(.);
/* Literals are also RO data. */
_lit4_start = ABSOLUTE(.);
*(*.lit4)
*(.lit4.*)
*(.gnu.linkonce.lit4.*)
_lit4_end = ABSOLUTE(.);
. = ALIGN(4);
_thread_local_start = ABSOLUTE(.);
*(.tdata)
*(.tdata.*)
*(.tbss)
*(.tbss.*)
_thread_local_end = ABSOLUTE(.);
. = ALIGN(4);
} >drom0_0_seg
.flash.text :
{
_stext = .;
_text_start = ABSOLUTE(.);
mapping[flash_text]
*(.stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
*(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
/* Similar to _iram_start, this symbol goes here so it is
resolved by addr2line in preference to the first symbol in
the flash.text segment.
*/
_flash_cache_start = ABSOLUTE(0);
} >iram0_2_seg
}

View File

@ -0,0 +1,632 @@
#!/usr/bin/env python
#
# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import unittest
import sys
import os
sys.path.append('../')
from fragments import *
from pyparsing import *
from sdkconfig import *
class FragmentTest(unittest.TestCase):
def parse(self, text):
self.parser.ignore("#" + restOfLine)
fragment = self.parser.parseString(text, parseAll=True)
return fragment[0]
class SectionsTest(FragmentTest):
def setUp(self):
self.parser = Sections.get_fragment_grammar()
def test_valid_entries(self):
valid_entries = """
[sections:test]
entries:
.section1
.section2
# Section 3 should not exist
# section3
.section4
# This is a comment
.section5
"""
sections = self.parse(valid_entries)
self.assertEqual("test", sections.name)
entries = sections.entries
expected = {
".section1",
".section2",
".section4",
".section5"
}
self.assertEqual(set(entries), expected)
def test_blank_entries(self):
blank_entries = """
[sections:test]
entries:
"""
with self.assertRaises(ParseException):
sections = self.parse(blank_entries)
def test_invalid_names(self):
with_spaces = """
[sections:invalid name 1]
entries:
"""
begins_with_number = """
[sections:2invalid_name]
entries:
"""
with_special_character = """
[sections:invalid_name~]
entries:
"""
with self.assertRaises(ParseException):
sections = self.parse(with_spaces)
with self.assertRaises(ParseException):
sections = self.parse(begins_with_number)
with self.assertRaises(ParseException):
sections = self.parse(with_special_character)
def test_non_existent_entries(self):
misspelled_entries_field = """
[sections:test]
entrie:
.section1
"""
missing_entries_field = """
[sections:test]
"""
with self.assertRaises(ParseException):
sections = self.parse(misspelled_entries_field)
with self.assertRaises(ParseException):
sections = self.parse(missing_entries_field)
def test_duplicate_entries(self):
duplicate_entries = """
[sections:test]
entries:
.section1
.section3
.section1
.section1
.section2
.section3
.section1
"""
sections = self.parse(duplicate_entries)
entries = sections.entries
expected = {
".section1",
".section2",
".section3",
}
self.assertEqual(set(entries), expected)
class SchemeTest(FragmentTest):
def setUp(self):
self.parser = Scheme.get_fragment_grammar()
def test_valid_entries(self):
valid_entries = """
[scheme:test]
entries:
sections1 -> target1
sections2 -> target2
"""
scheme = self.parse(valid_entries)
entries = scheme.entries
expected = {
("sections1", "target1"),
("sections2", "target2")
}
self.assertEqual(entries, expected)
def test_duplicate_same_mapping(self):
duplicate_entries = """
[scheme:duplicate_same_mapping]
entries:
sections1 -> target1
sections2 -> target2
sections1 -> target1
"""
scheme = self.parse(duplicate_entries)
entries = scheme.entries
expected = {
("sections1", "target1"),
("sections2", "target2")
}
self.assertEqual(len(entries), 2)
self.assertEqual(entries, expected)
def test_invalid_separator(self):
wrong_character = """
[scheme:test]
entries:
sections1, target1
"""
single_word = """
[scheme:test]
entries:
sections1
"""
with self.assertRaises(ParseException):
scheme = self.parse(wrong_character)
with self.assertRaises(ParseException):
scheme = self.parse(single_word)
def test_blank_entries(self):
blank_entries = """
[scheme:test]
entries:
"""
with self.assertRaises(ParseException):
sections = self.parse(blank_entries)
def test_non_existent_entries(self):
misspelled_entries_field = """
[scheme:test]
entrie:
section -> target
"""
missing_entries_field = """
[scheme:test]
"""
with self.assertRaises(ParseException):
sections = self.parse(misspelled_entries_field)
with self.assertRaises(ParseException):
sections = self.parse(missing_entries_field)
class MappingTest(FragmentTest):
def setUp(self):
self.parser = Mapping.get_fragment_grammar()
def parse_expression(self, expression):
parser = SDKConfig.get_expression_grammar()
return parser.parseString(expression, parseAll=True)
def test_valid_grammar(self):
valid_entries = """
[mapping]
archive: lib.a
entries:
obj:symbol (noflash)
# Comments should not matter
obj (noflash)
# Nor should whitespace
obj : symbol_2 ( noflash )
obj_2 ( noflash )
* (noflash)
"""
mapping = self.parse(valid_entries)
self.assertEqual("lib.a", mapping.archive)
self.assertEqual("lib_a", mapping.name)
entries = mapping.entries
expected = [("default", {
("obj", "symbol", "noflash"),
("obj", None, "noflash"),
("obj", "symbol_2", "noflash"),
("obj_2", None, "noflash"),
("*", None, "noflash")
} ) ]
self.assertEqual(entries, expected)
def test_invalid_grammar(self):
with_fragment_name = """
[mapping:name]
archive: lib.a
entries:
obj:symbol (noflash)
"""
missing_archive = """
[mapping:name]
entries:
obj:symbol (noflash)
"""
misspelled_archive = """
[mapping:name]
archi: lib.a
entries:
obj:symbol (noflash)
"""
missing_entries = """
[mapping]
archive: lib.a
"""
misspelled_entries = """
[mapping]
archive: lib.a
entrie:
obj:symbol (noflash)
"""
missing_symbols = """
[mapping]
archive: lib.a
entries:
obj: (noflash)
"""
missing_scheme_1 = """
[mapping]
archive: lib.a
entries:
obj: ()
"""
missing_scheme_2 = """
[mapping]
archive: lib.a
entries:
obj:symbol
"""
missing_entity = """
[mapping]
archive: lib.a
entries:
(noflash)
"""
wilcard_symbol = """
[mapping]
archive: lib.a
entries:
obj:* (noflash)
"""
empty_object_with_symbol = """
[mapping]
archive: lib.a
entries:
:symbol (noflash)
"""
wildcard_object_with_symbol = """
[mapping]
archive: lib.a
entries:
*:symbol (noflash)
"""
empty_definition = """
[mapping]
"""
with self.assertRaises(ParseException):
sections = self.parse(with_fragment_name)
with self.assertRaises(ParseException):
sections = self.parse(missing_archive)
with self.assertRaises(ParseException):
sections = self.parse(misspelled_archive)
with self.assertRaises(ParseException):
sections = self.parse(missing_entries)
with self.assertRaises(ParseException):
sections = self.parse(misspelled_entries)
with self.assertRaises(ParseException):
sections = self.parse(missing_symbols)
with self.assertRaises(ParseException):
sections = self.parse(missing_scheme_1)
with self.assertRaises(ParseException):
sections = self.parse(missing_scheme_2)
with self.assertRaises(ParseException):
sections = self.parse(missing_entity)
with self.assertRaises(ParseException):
sections = self.parse(wilcard_symbol)
with self.assertRaises(ParseException):
sections = self.parse(empty_object_with_symbol)
with self.assertRaises(ParseException):
sections = self.parse(wildcard_object_with_symbol)
with self.assertRaises(ParseException):
sections = self.parse(empty_definition)
def test_explicit_blank_default_w_others(self):
expl_blnk_w_oth = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_a (noflash)
: default
"""
mapping = self.parse(expl_blnk_w_oth)
entries = mapping.entries
expected = [ ( entries[0][0] , {
("obj_a", None, "noflash"),
} ),
("default", set() ) ]
self.assertEqual(entries, expected)
def test_implicit_blank_default_w_others(self):
impl_blnk_w_oth = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_a (noflash)
"""
mapping = self.parse(impl_blnk_w_oth)
entries = mapping.entries
expected = [ ( entries[0][0] , {
("obj_a", None, "noflash"),
} ),
("default", set() ) ]
self.assertEqual(entries, expected)
def test_explicit_blank_default(self):
expl_blnk_def = """
[mapping]
archive: lib.a
entries:
: default
"""
mapping = self.parse(expl_blnk_def)
entries = mapping.entries
expected = [ ("default", set() ) ]
self.assertEqual(entries, expected)
def test_implicit_blank_default(self):
impl_blnk_def = """
[mapping]
archive: lib.a
entries:
: default
"""
mapping = self.parse(impl_blnk_def)
entries = mapping.entries
expected = [ ("default", set() ) ]
self.assertEqual(entries, expected)
def test_multiple_entries(self):
multiple_entries = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_a1 (noflash)
obj_a2 (noflash)
: CONFIG_B = y
obj_b1 (noflash)
obj_b2 (noflash)
obj_b3 (noflash)
: CONFIG_C = y
obj_c1 (noflash)
"""
mapping = self.parse(multiple_entries)
entries = mapping.entries
expected = [ ( entries[0][0] , {
("obj_a1", None, "noflash"),
("obj_a2", None, "noflash"),
} ),
( entries[1][0] , {
("obj_b1", None, "noflash"),
("obj_b2", None, "noflash"),
("obj_b3", None, "noflash"),
} ),
( entries[2][0] , {
("obj_c1", None, "noflash"),
} ),
("default", set() ) ]
self.assertEqual(entries, expected)
def test_blank_entries(self):
blank_entries = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_a (noflash)
: CONFIG_B = y
: CONFIG_C = y
obj_c (noflash)
: CONFIG_D = y
: CONFIG_E = y
: default
obj (noflash)
"""
mapping = self.parse(blank_entries)
entries = mapping.entries
expected = [ ( entries[0][0] , {
("obj_a", None, "noflash")
} ),
( entries[1][0] , set()),
( entries[2][0] , {
("obj_c", None, "noflash")
} ),
( entries[3][0] , set()),
( entries[4][0] , set()),
( "default" , {
("obj", None, "noflash")
} ) ]
self.assertEqual(entries, expected)
def test_blank_first_condition(self):
blank_first_condition = """
[mapping]
archive: lib.a
entries:
obj_a (noflash)
: CONFIG_B = y
obj_b (noflash)
"""
with self.assertRaises(ParseException):
mapping = self.parse(blank_first_condition)
def test_nonlast_default(self):
nonlast_default_1 = """
[mapping]
archive: lib.a
entries:
: default
obj_a (noflash)
: CONFIG_A = y
obj_A (noflash)
"""
nonlast_default_2 = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_A (noflash)
: default
obj_a (noflash)
: CONFIG_B = y
obj_B (noflash)
"""
nonlast_default_3 = """
[mapping]
archive: lib.a
entries:
: CONFIG_A = y
obj_A (noflash)
:
obj_a (noflash)
: CONFIG_B = y
obj_B (noflash)
"""
with self.assertRaises(ParseException):
mapping = self.parse(nonlast_default_1)
with self.assertRaises(ParseException):
mapping = self.parse(nonlast_default_2)
with self.assertRaises(ParseException):
mapping = self.parse(nonlast_default_3)
def test_duplicate_default(self):
duplicate_default_1 = """
archive: lib.a
entries:
: CONFIG_A = y
obj_A (noflash)
: default
obj_a (noflash)
: CONFIG_B = y
obj_B (noflash)
: default
obj_a (noflash)
"""
duplicate_default_2 = """
archive: lib.a
entries:
: CONFIG_A = y
obj_A (noflash)
: CONFIG_B = y
obj_a (noflash)
: default
obj_B (noflash)
:
obj_a (noflash)
"""
with self.assertRaises(ParseException):
mapping = self.parse(duplicate_default_1)
with self.assertRaises(ParseException):
mapping = self.parse(duplicate_default_2)
if __name__ =="__main__":
unittest.main()

File diff suppressed because it is too large Load Diff