code_relocation: Add NOCOPY feature

* Use case of interest:

Some platforms are shipping in parallel to the internal FLASH some other
storage / external FLASH (usually a QSPI FLASH) that can be used to
execute (XIP) code from.

The content of this external FLASH can usually be written at flash time
using proper tools (see for example the case of the external FLASH on
the nRF5340DK that can be written at flash time using nrfjprog).

The external FLASH is a nice addition that is extremely useful when a
large application code doesn't entirely fit on the internal FLASH so
that we could want to move part of it in the auxiliary FLASH to XIP the
code from there.

* The problem:

Right now Zephyr doesn't have a formal and generic way to move at build
time part of the code to a different memory region.

* The current status:

Zephyr is indeed shipping a code_relocation feature but that doesn't
entirely match our needs.

When XIP is enabled, the code_relocation feature is used in Zephyr to
move the selected code (that is to copy text section, to initialize data
and zero bss) from FLASH to SRAM at run time and execute code from SRAM.

The relocation is done by a generated snippet of code that is
memcpy()-ing the right content to the destination region also using some
build-time generated portions of linker script to retrieve start and
destination addresses and regions.

* This patch:

This patch is leveraging the code_relocation code and adding a NOCOPY
feature. This feature is using the code_relocation feature to
dynamically build the linker script snippets but entirely skipping the
run-time code relocation so that the code can be XIP-ed from the
destination region.

* Example:

Let's say that we have a big file called `huge_file.c` that we want to
XIP from the external FLASH that is mapped in an EXTFLASH region.

In this case we should enable `CONFIG_XIP` and
`CONFIG_CODE_DATA_RELOCATION` and instruct cmake as follows:

  zephyr_code_relocate(src/huge_file.c EXTFLASH_TEXT NOCOPY)
  zephyr_code_relocate(src/huge_file.c SRAM_DATA)

this means that:

- The .text section of the `huge_file.c` must reside in the EXTFLASH
  memory region and we do not need to copy the section there because we
  are going to XIP it (and we assume that the file is going to be placed
  in the external FLASH at flash time).
- The .data section of the `huge_file.c` must still reside in the SRAM
  memory region.

* TODOs:

It's desirable to have the possibility to relocate libraries and
pre-build files instead of source code files.

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
This commit is contained in:
Carlo Caione 2022-02-15 21:18:50 +01:00 committed by Anas Nashif
commit d3203dc70d
11 changed files with 231 additions and 16 deletions

View file

@ -1261,14 +1261,22 @@ endfunction(zephyr_linker_sources)
# Helper function for CONFIG_CODE_DATA_RELOCATION
# Call this function with 2 arguments file and then memory location
# Call this function with 2 arguments file and then memory location.
# One optional [NOCOPY] flag can be used.
function(zephyr_code_relocate file location)
set(options NOCOPY)
cmake_parse_arguments(CODE_REL "${options}" "" "" ${ARGN})
if(NOT IS_ABSOLUTE ${file})
set(file ${CMAKE_CURRENT_SOURCE_DIR}/${file})
endif()
if(NOT CODE_REL_NOCOPY)
set(copy_flag COPY)
else()
set(copy_flag NOCOPY)
endif()
set_property(TARGET code_data_relocation_target
APPEND PROPERTY COMPILE_DEFINITIONS
"${location}:${file}")
"${location}:${file}:${copy_flag}")
endfunction()
# Usage:

View file

@ -91,6 +91,23 @@ This section shows additional configuration options that can be set in
* Multiple regions can also be appended together such as: SRAM2_DATA_BSS.
This will place data and bss inside SRAM2.
NOCOPY flag
===========
When a ``NOCOPY`` option is passed to the ``zephyr_code_relocate()`` function,
the relocation code is not generated in ``code_relocation.c``. This flag can be
used when we want to move the content of a specific file (or set of files) to a
XIP area.
This example will place the .text section of the ``xip_external_flash.c`` file
to the ``EXTFLASH`` memory region where it will be executed from (XIP). The
.data will be relocated as usual into SRAM.
.. code-block:: none
zephyr_code_relocate(src/xip_external_flash.c EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(src/xip_external_flash.c SRAM_DATA)
Sample
======
A sample showcasing this feature is provided at
@ -100,3 +117,6 @@ This is an example of using the code relocation feature.
This example will place .text, .data, .bss from 3 files to various parts in the SRAM
using a custom linker file derived from ``include/arch/arm/aarch32/cortex_m/scripts/linker.ld``
A sample showcasing the NOCOPY flag is provided at
``$ZEPHYR_BASE/samples/application_development/code_relocation_nocopy/``

View file

@ -0,0 +1,18 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(code_relocation_nocopy)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
# Run ext_code from the external flash (XIP). No need to copy.
zephyr_code_relocate(src/ext_code.c EXTFLASH_TEXT NOCOPY)
# But still relocate (copy) the data to SRAM
zephyr_code_relocate(src/ext_code.c SRAM_DATA)
# sram_code instead runs entirely from SRAM after being copied there.
zephyr_code_relocate(src/sram_code.c SRAM)

View file

@ -0,0 +1,13 @@
.. _code_relocation_nocopy:
Code relocation nocopy
######################
Overview
********
A simple example that demonstrates how relocation of code, data or bss sections
using a custom linker script.
Differently from the code relocation sample, this sample is relocating the
content of the ext_code.c file to a different FLASH section and the code is XIP
directly from there without the need to copy / relocate the code.

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Linker command/script file
*
* Linker script for the Cortex-M platforms.
*/
#include <autoconf.h>
#include <linker/sections.h>
#include <devicetree.h>
#include <linker/linker-defs.h>
#include <linker/linker-tool.h>
/*
* Add another fake portion of FLASH to simulate a secondary or external FLASH
* that we can do XIP from.
*/
#define EXTFLASH_ADDR 0x5000
#define EXTFLASH_SIZE (CONFIG_FLASH_SIZE * 1K - EXTFLASH_ADDR)
MEMORY
{
EXTFLASH (wx) : ORIGIN = 0x5000, LENGTH = EXTFLASH_SIZE
}
#include <arch/arm/aarch32/cortex_m/scripts/linker.ld>

View file

@ -0,0 +1,5 @@
CONFIG_CODE_DATA_RELOCATION=y
CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld"
CONFIG_COVERAGE=n
CONFIG_XIP=y

View file

@ -0,0 +1,12 @@
sample:
description: Code data relocation nocopy Sample
name: code relocation nocopy
tests:
sample.application_development.code_relocation_nocopy:
platform_allow: qemu_cortex_m3
tags: linker
harness: console
harness_config:
type: one_line
regex:
- "Hello World! (.*)"

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/printk.h>
uint32_t var_ext_sram_data = 10U;
void function_in_ext_flash(void)
{
printk("Address of %s %p\n", __func__, &function_in_ext_flash);
printk("Address of var_ext_sram_data %p (%d)\n", &var_ext_sram_data, var_ext_sram_data);
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018 Intel Corporation.
* Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <sys/printk.h>
/*
* This function will allow execute from sram region. This is needed only for
* this sample because by default all soc will disable the execute from SRAM.
* An application that requires that the code be executed from SRAM will have
* to configure the region appropriately in arm_mpu_regions.c.
*/
#ifdef CONFIG_ARM_MPU
#include <arch/arm/aarch32/cortex_m/cmsis.h>
void disable_mpu_rasr_xn(void)
{
uint32_t index;
/*
* Kept the max index as 8(irrespective of soc) because the sram would
* most likely be set at index 2.
*/
for (index = 0U; index < 8; index++) {
MPU->RNR = index;
if (MPU->RASR & MPU_RASR_XN_Msk) {
MPU->RASR ^= MPU_RASR_XN_Msk;
}
}
}
#endif /* CONFIG_ARM_MPU */
extern void function_in_ext_flash(void);
extern void function_in_sram(void);
void main(void)
{
#ifdef CONFIG_ARM_MPU
disable_mpu_rasr_xn();
#endif /* CONFIG_ARM_MPU */
printk("Address of %s function %p\n", __func__, &main);
function_in_ext_flash();
function_in_sram();
printk("Hello World! %s\n", CONFIG_BOARD);
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/printk.h>
uint32_t var_sram_data = 10U;
void function_in_sram(void)
{
printk("Address of %s %p\n", __func__, &function_in_sram);
printk("Address of var_sram_data %p (%d)\n", &var_sram_data, var_sram_data);
}

View file

@ -12,8 +12,9 @@ are given to this python script in the form of a string.
Example of such a string would be::
SRAM2:/home/xyz/zephyr/samples/hello_world/src/main.c,\
SRAM1:/home/xyz/zephyr/samples/hello_world/src/main2.c
SRAM2:COPY:/home/xyz/zephyr/samples/hello_world/src/main.c,\
SRAM1:COPY:/home/xyz/zephyr/samples/hello_world/src/main2.c, \
FLASH2:NOCOPY:/home/xyz/zephyr/samples/hello_world/src/main3.c
To invoke this script::
@ -26,6 +27,8 @@ Configuration that needs to be sent to the python script.
- If the memory type is appended with _DATA / _TEXT/ _RODATA/ _BSS only the
selected memory is placed in the required memory region. Others are
ignored.
- COPY/NOCOPY defines whether the script should generate the relocation code in
code_relocation.c or not
Multiple regions can be appended together like SRAM2_DATA_BSS
this will place data and bss inside SRAM2.
@ -56,6 +59,11 @@ GROUP_DATA_LINK_IN({0}, FLASH)
GROUP_DATA_LINK_IN({0}, {0})
#endif
"""
LOAD_ADDRESS_LOCATION_FLASH_NOCOPY = """
GROUP_LINK_IN({0})
"""
LOAD_ADDRESS_LOCATION_BSS = "GROUP_LINK_IN({0})"
MPU_RO_REGION_START = """
@ -236,10 +244,13 @@ def print_linker_sections(list_sections):
def string_create_helper(region, memory_type,
full_list_of_sections, load_address_in_flash):
full_list_of_sections, load_address_in_flash, is_copy):
linker_string = ''
if load_address_in_flash:
load_address_string = LOAD_ADDRESS_LOCATION_FLASH.format(memory_type)
if is_copy:
load_address_string = LOAD_ADDRESS_LOCATION_FLASH.format(memory_type)
else:
load_address_string = LOAD_ADDRESS_LOCATION_FLASH_NOCOPY.format(memory_type)
else:
load_address_string = LOAD_ADDRESS_LOCATION_BSS.format(memory_type)
if full_list_of_sections[region]:
@ -278,19 +289,24 @@ def generate_linker_script(linker_file, sram_data_linker_file, sram_bss_linker_f
for memory_type, full_list_of_sections in \
sorted(complete_list_of_sections.items()):
if memory_type != "SRAM":
is_copy = bool("|COPY" in memory_type)
memory_type = memory_type.split("|", 1)[0]
if memory_type != "SRAM" and is_copy:
gen_string += MPU_RO_REGION_START.format(memory_type.lower(), memory_type.upper())
gen_string += string_create_helper("text", memory_type, full_list_of_sections, 1)
gen_string += string_create_helper("rodata", memory_type, full_list_of_sections, 1)
if memory_type != "SRAM":
gen_string += string_create_helper("text", memory_type, full_list_of_sections, 1, is_copy)
gen_string += string_create_helper("rodata", memory_type, full_list_of_sections, 1, is_copy)
if memory_type != "SRAM" and is_copy:
gen_string += MPU_RO_REGION_END.format(memory_type.lower())
if memory_type == 'SRAM':
gen_string_sram_data += string_create_helper("data", memory_type, full_list_of_sections, 1)
gen_string_sram_bss += string_create_helper("bss", memory_type, full_list_of_sections, 0)
gen_string_sram_data += string_create_helper("data", memory_type, full_list_of_sections, 1, 1)
gen_string_sram_bss += string_create_helper("bss", memory_type, full_list_of_sections, 0, 1)
else:
gen_string += string_create_helper("data", memory_type, full_list_of_sections, 1)
gen_string += string_create_helper("bss", memory_type, full_list_of_sections, 0)
gen_string += string_create_helper("data", memory_type, full_list_of_sections, 1, 1)
gen_string += string_create_helper("bss", memory_type, full_list_of_sections, 0, 1)
# finally writing to the linker file
with open(linker_file, "w") as file_desc:
@ -402,7 +418,7 @@ def create_dict_wrt_mem():
if ':' not in line:
continue
mem_region, file_name = line.split(':', 1)
mem_region, file_name, copy_flag = line.split(':', 2)
file_name_list = glob.glob(file_name)
if not file_name_list:
@ -412,6 +428,9 @@ def create_dict_wrt_mem():
continue
if args.verbose:
print("Memory region ", mem_region, " Selected for file:", file_name_list)
mem_region = "|".join((mem_region, copy_flag))
if mem_region in rel_dict:
rel_dict[mem_region].extend(file_name_list)
else:
@ -456,7 +475,10 @@ def main():
code_generation = {"copy_code": '', "zero_code": '', "extern": ''}
for mem_type, list_of_sections in sorted(complete_list_of_sections.items()):
code_generation = generate_memcpy_code(mem_type,
if "|COPY" in mem_type:
mem_type = mem_type.split("|", 1)[0]
code_generation = generate_memcpy_code(mem_type,
list_of_sections, code_generation)
dump_header_file(args.output_code, code_generation)