Skip to content

Commit

Permalink
rp2_common/pico_standard_link: implement customizable linker script u…
Browse files Browse the repository at this point in the history
…sing C preprocessor

Background:

For cases where modifications to the linker script are required, pico-sdk only
provides the option to provide an entire linker script using
pico_set_linker_script. This patch adds a function pico_customize_linker_script
to create a linker script in the build process, based on a template provided by
pico-sdk and local settings provided by the user.

Use cases for linker script modification include:
- using the linker script as a rudimentary file system for binary blobs
- sharing WiFi firmware blobs between bootloader and application (picowota)
- reducing the number of code duplication in linker scripts (e.g.
  memmap_blocked_ram.ld which differs in only one line from the default)

In such cases, deriving the linker script from a template may/should lead to
code that is easier to maintain.

Implementation:

The template is memmap.ld.in, the user input is provided by specifying a
customization file to be included, and the linker script is generated by the C
preprocessor.

The template exposes a few settings by #defining them before including the
customization file, and provides a few hooks to add elements to the template.

Examples and hints for use, based on a working example where cyw43 firmware
lives in a separate region in flash:

in CMakeLists.txt:

    pico_customize_linker_script(my_target tweaks.h)

tweaks.h:

    #undef MAIN_FLASH_LENGTH
    #define MAIN_FLASH_LENGTH 256k

    #undef ADDITIONAL_FLASH_REGIONS
    #define ADDITIONAL_FLASH_REGIONS \
        FLASH_CYWFW(rx):            ORIGIN = 0x10040000, LENGTH = 256k

    #undef ADDITIONAL_SECTIONS
    #define ADDITIONAL_SECTIONS \
        .fw_cyw43 : {               \\
            . = ALIGN(4k);          \\
            KEEP(*(.fw_cyw43.meta)) \\
            . = ALIGN(512);         \\
            *(.fw_cyw43.blob)       \\
        } > FLASH_CYWFW

Details:

- The linker script will be a build product named ${TARGET}.ld
- When using \\ at the end of lines, newlines are inserted in the resulting
  linker script in a postprocessing step. This is done to improve readability
  of the resulting linker script.
- Lines starting with # need to be removed from the output; they are now turned
  into /*comments*/
- Values that are to be overridden need to be #undeffed before redefining them,
  which is a bit awkward; another option is to use #ifdef for each and every
  variable in the template; yet another option (for integers) is to use linker
  variables (i.e. simply setting MAIN_FLASH_LENGTH=256k).
  • Loading branch information
mvds00 committed Nov 17, 2023
1 parent 6a7db34 commit de38bb5
Show file tree
Hide file tree
Showing 2 changed files with 284 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/rp2_common/pico_standard_link/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ if (NOT TARGET pico_standard_link)
pico_add_link_depend(${TARGET} ${LDSCRIPT})
endfunction()

function(pico_customize_linker_script TARGET LDSCRIPT_INCLUDE)
set(LDSCRIPT_INPUT ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/memmap.ld.in)
set(LDSCRIPT_GENERATED ${TARGET}.ld)
message("generating linker script at ${LDSCRIPT_GENERATED} from ${LDSCRIPT_INPUT} and ${LDSCRIPT_INCLUDE}")

add_custom_command(TARGET ${TARGET} DEPENDS ${LDSCRIPT_INCLUDE} PRE_BUILD
COMMAND ${CMAKE_C_COMPILER} -E -x c -CC ${CFLAGS} -DLD_INCLUDE=\"${LDSCRIPT_INCLUDE}\" ${LDSCRIPT_INPUT} | sed -e "s@^#.*@/*&*/@" | tr "\\\\" "\\n" > ${LDSCRIPT_GENERATED}
VERBATIM
)
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_LINKER_SCRIPT ${LDSCRIPT_GENERATED})
endfunction()

function(pico_set_binary_type TARGET TYPE)
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_BINARY_TYPE ${TYPE})
endfunction()
Expand Down
272 changes: 272 additions & 0 deletions src/rp2_common/pico_standard_link/memmap.ld.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/* Based on GCC ARM embedded samples.
Defines the following symbols for use by code:
__exidx_start
__exidx_end
__etext
__data_start__
__preinit_array_start
__preinit_array_end
__init_array_start
__init_array_end
__fini_array_start
__fini_array_end
__data_end__
__bss_start__
__bss_end__
__end__
end
__HeapLimit
__StackLimit
__StackTop
__stack (== StackTop)
*/

/* defaults that can be redefined */
#define MAIN_FLASH_LENGTH 2048k
#define MAIN_RAM_LENGTH 256k
#define MAIN_RAM_ORIGIN 0x20000000
#define MAIN_ROM_REGION FLASH

#define ADDITIONAL_FLASH_REGIONS
#define ADDITIONAL_RAM_REGIONS
#define ADDITIONAL_SECTIONS

#ifdef LD_INCLUDE
#include LD_INCLUDE
#endif

MEMORY
{
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = MAIN_FLASH_LENGTH
ADDITIONAL_FLASH_REGIONS
RAM(rwx) : ORIGIN = MAIN_RAM_ORIGIN, LENGTH = MAIN_RAM_LENGTH
ADDITIONAL_RAM_REGIONS
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
}

ENTRY(_entry_point)

SECTIONS
{
/* Second stage bootloader is prepended to the image. It must be 256 bytes big
and checksummed. It is usually built by the boot_stage2 target
in the Raspberry Pi Pico SDK
*/

.flash_begin : {
__flash_binary_start = .;
} > FLASH

.boot2 : {
__boot2_start__ = .;
KEEP (*(.boot2))
__boot2_end__ = .;
} > FLASH

ASSERT(__boot2_end__ - __boot2_start__ == 256,
"ERROR: Pico second stage bootloader must be 256 bytes in size")

/* The second stage will always enter the image at the start of .text.
The debugger will use the ELF entry point, which is the _entry_point
symbol if present, otherwise defaults to start of .text.
This can be used to transfer control back to the bootrom on debugger
launches only, to perform proper flash setup.
*/

.vectors : {
__logical_binary_start = .;
KEEP (*(.vectors))
} > MAIN_ROM_REGION

.text : {
KEEP (*(.binary_info_header))
__binary_info_header_end = .;
KEEP (*(.reset))
/* TODO revisit this now memset/memcpy/float in ROM */
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
* FLASH ... we will include any thing excluded here in .data below by default */
*(.init)
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
*(.fini)
/* Pull all c'tors into .text */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* Followed by destructors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)

*(.eh_frame*)
. = ALIGN(4);
} > MAIN_ROM_REGION

ADDITIONAL_SECTIONS

.rodata : {
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
. = ALIGN(4);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
. = ALIGN(4);
} > MAIN_ROM_REGION

.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > MAIN_ROM_REGION

__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > MAIN_ROM_REGION
__exidx_end = .;

/* Machine inspectable binary information */
. = ALIGN(4);
__binary_info_start = .;
.binary_info :
{
KEEP(*(.binary_info.keep.*))
*(.binary_info.*)
} > MAIN_ROM_REGION
__binary_info_end = .;
. = ALIGN(4);

.ram_vector_table (NOLOAD): {
*(.ram_vector_table)
} > RAM

.uninitialized_data (NOLOAD): {
. = ALIGN(4);
*(.uninitialized_data*)
} > RAM

.data : {
__data_start__ = .;
*(vtable)

*(.time_critical*)

/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
*(.text*)
. = ALIGN(4);
*(.rodata*)
. = ALIGN(4);

*(.data*)

. = ALIGN(4);
*(.after_data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__mutex_array_start = .);
KEEP(*(SORT(.mutex_array.*)))
KEEP(*(.mutex_array))
PROVIDE_HIDDEN (__mutex_array_end = .);

. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(SORT(.preinit_array.*)))
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);

. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);

. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);

*(.jcr)
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM AT> MAIN_ROM_REGION
/* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */
__etext = LOADADDR(.data);

/* Start and end symbols must be word-aligned */
.scratch_x : {
__scratch_x_start__ = .;
*(.scratch_x.*)
. = ALIGN(4);
__scratch_x_end__ = .;
} > SCRATCH_X AT > MAIN_ROM_REGION
__scratch_x_source__ = LOADADDR(.scratch_x);

.scratch_y : {
__scratch_y_start__ = .;
*(.scratch_y.*)
. = ALIGN(4);
__scratch_y_end__ = .;
} > SCRATCH_Y AT > MAIN_ROM_REGION
__scratch_y_source__ = LOADADDR(.scratch_y);

.bss : {
. = ALIGN(4);
__bss_start__ = .;
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM

.heap (NOLOAD):
{
__end__ = .;
end = __end__;
KEEP(*(.heap*))
__HeapLimit = .;
} > RAM

/* .stack*_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later
*
* stack1 section may be empty/missing if platform_launch_core1 is not used */

/* by default we put core 0 stack at the end of scratch Y, so that if core 1
* stack is not used then all of SCRATCH_X is free.
*/
.stack1_dummy (NOLOAD):
{
*(.stack1*)
} > SCRATCH_X
.stack_dummy (NOLOAD):
{
KEEP(*(.stack*))
} > SCRATCH_Y

.flash_end : {
PROVIDE(__flash_binary_end = .);
} > MAIN_ROM_REGION

/* stack limit is poorly named, but historically is maximum heap ptr */
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);

/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")

ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
/* todo assert on extra code */
}

0 comments on commit de38bb5

Please sign in to comment.