diff --git a/CODEOWNERS b/CODEOWNERS index e8722d1e901ef..667a4a4573a20 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -725,6 +725,7 @@ scripts/build/gen_image_info.py @tejlmand /scripts/build/uf2conv.py @petejohanson /scripts/build/user_wordsize.py @cfriedt /scripts/valgrind.supp @aescolar @daor-oti +/share/sysbuild/ @tejlmand /share/zephyr-package/ @tejlmand /share/zephyrunittest-package/ @tejlmand /subsys/bluetooth/ @alwa-nordic @jhedberg @Vudentz diff --git a/cmake/modules/kconfig.cmake b/cmake/modules/kconfig.cmake index e13ac293f3379..e72cb65a00ec9 100644 --- a/cmake/modules/kconfig.cmake +++ b/cmake/modules/kconfig.cmake @@ -17,6 +17,8 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${AUTOCONF_H}) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig/include/generated) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig/include/config) +set_ifndef(KCONFIG_NAMESPACE "CONFIG") + # Support multiple SOC_ROOT, remove ZEPHYR_BASE as that is always sourced. set(kconfig_soc_root ${SOC_ROOT}) list(REMOVE_ITEM kconfig_soc_root ${ZEPHYR_BASE}) @@ -59,7 +61,7 @@ else() set(KCONFIG_ROOT ${ZEPHYR_BASE}/Kconfig) endif() -set(BOARD_DEFCONFIG ${BOARD_DIR}/${BOARD}_defconfig) +set_ifndef(BOARD_DEFCONFIG ${BOARD_DIR}/${BOARD}_defconfig) set(DOTCONFIG ${PROJECT_BINARY_DIR}/.config) set(PARSED_KCONFIG_SOURCES_TXT ${PROJECT_BINARY_DIR}/kconfig/sources.txt) @@ -109,6 +111,7 @@ set(COMMON_KCONFIG_ENV_SETTINGS PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} srctree=${ZEPHYR_BASE} KERNELVERSION=${KERNELVERSION} + CONFIG_=${KCONFIG_NAMESPACE}_ KCONFIG_CONFIG=${DOTCONFIG} # Set environment variables so that Kconfig can prune Kconfig source # files for other architectures @@ -146,10 +149,10 @@ set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_hardenconfig ${ZEPHYR_BASE}/scripts/kconfig/hardenconfig.py ) +set_ifndef(KCONFIG_TARGETS menuconfig guiconfig hardenconfig) + foreach(kconfig_target - menuconfig - guiconfig - hardenconfig + ${KCONFIG_TARGETS} ${EXTRA_KCONFIG_TARGETS} ) add_custom_target( @@ -170,15 +173,15 @@ foreach(kconfig_target endforeach() # Support assigning Kconfig symbols on the command-line with CMake -# cache variables prefixed with 'CONFIG_'. This feature is -# experimental and undocumented until it has undergone more +# cache variables prefixed according to the Kconfig namespace. +# This feature is experimental and undocumented until it has undergone more # user-testing. unset(EXTRA_KCONFIG_OPTIONS) get_cmake_property(cache_variable_names CACHE_VARIABLES) foreach (name ${cache_variable_names}) - if("${name}" MATCHES "^CONFIG_") - # When a cache variable starts with 'CONFIG_', it is assumed to be - # a Kconfig symbol assignment from the CMake command line. + if("${name}" MATCHES "^${KCONFIG_NAMESPACE}_") + # When a cache variable starts with the 'KCONFIG_NAMESPACE' value, it is + # assumed to be a Kconfig symbol assignment from the CMake command line. set(EXTRA_KCONFIG_OPTIONS "${EXTRA_KCONFIG_OPTIONS}\n${name}=${${name}}" ) @@ -316,18 +319,18 @@ add_custom_target(config-twister DEPENDS ${DOTCONFIG}) # CMakeCache.txt. If the symbols end up in DOTCONFIG they will be # re-introduced to the namespace through 'import_kconfig'. foreach (name ${cache_variable_names}) - if("${name}" MATCHES "^CONFIG_") + if("${name}" MATCHES "^${KCONFIG_NAMESPACE}_") unset(${name}) unset(${name} CACHE) endif() endforeach() -# Parse the lines prefixed with CONFIG_ in the .config file from Kconfig -import_kconfig(CONFIG_ ${DOTCONFIG}) +# Import the .config file and make all settings available in CMake processing. +import_kconfig(${KCONFIG_NAMESPACE} ${DOTCONFIG}) # Re-introduce the CLI Kconfig symbols that survived foreach (name ${cache_variable_names}) - if("${name}" MATCHES "^CONFIG_") + if("${name}" MATCHES "^${KCONFIG_NAMESPACE}_") if(DEFINED ${name}) set(${name} ${${name}} CACHE STRING "") endif() diff --git a/scripts/west_commands/build.py b/scripts/west_commands/build.py index 3e72bc9dea27d..c4d0e09b851d5 100644 --- a/scripts/west_commands/build.py +++ b/scripts/west_commands/build.py @@ -12,17 +12,21 @@ from west import log from west.configuration import config from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache -from build_helpers import is_zephyr_build, find_build_dir, \ +from build_helpers import is_zephyr_build, find_build_dir, load_domains, \ FIND_BUILD_DIR_DESCRIPTION from zephyr_ext_common import Forceable _ARG_SEPARATOR = '--' +SYSBUILD_PROJ_DIR = pathlib.Path(__file__).resolve().parent.parent.parent \ + / pathlib.Path('share/sysbuild') + BUILD_USAGE = '''\ west build [-h] [-b BOARD[@REV]]] [-d BUILD_DIR] [-t TARGET] [-p {auto, always, never}] [-c] [--cmake-only] [-n] [-o BUILD_OPT] [-f] + [--sysbuild | --no-sysbuild] [source_dir] -- [cmake_opt [cmake_opt ...]] ''' @@ -111,6 +115,9 @@ def do_add_parser(self, parser_adder): help='force a cmake run') group.add_argument('--cmake-only', action='store_true', help="just run cmake; don't build (implies -c)") + group.add_argument('--domain', action='append', + help='''execute build tool (make or ninja) only for + given domain''') group.add_argument('-t', '--target', help='''run build system target TARGET (try "-t usage")''') @@ -124,6 +131,13 @@ def do_add_parser(self, parser_adder): dest='dry_run', action='store_true', help="just print build commands; don't run them") + group = parser.add_mutually_exclusive_group() + group.add_argument('--sysbuild', action='store_true', + help='''create multi domain build system''') + group.add_argument('--no-sysbuild', action='store_true', + help='''do not create multi domain build system + (default)''') + group = parser.add_argument_group('pristine builds', PRISTINE_DESCRIPTION) group.add_argument('-p', '--pristine', choices=['auto', 'always', @@ -190,8 +204,9 @@ def do_run(self, args, remainder): self._sanity_check() self._update_cache() + self.domains = load_domains(self.build_dir) - self._run_build(args.target) + self._run_build(args.target, args.domain) def _find_board(self): board, origin = None, None @@ -357,9 +372,14 @@ def _sanity_check(self): # CMake configuration phase. self.run_cmake = True - cached_app = self.cmake_cache.get('APPLICATION_SOURCE_DIR') - log.dbg('APPLICATION_SOURCE_DIR:', cached_app, - level=log.VERBOSE_EXTREME) + cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR') + cached_app = self.cmake_cache.get('APP_DIR') + # if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates + # an older build folder, this still requires pristine. + if cached_app is None and cached_proj: + cached_app = cached_proj + + log.dbg('APP_DIR:', cached_app, level=log.VERBOSE_EXTREME) source_abs = (os.path.abspath(self.args.source_dir) if self.args.source_dir else None) cached_abs = os.path.abspath(cached_app) if cached_app else None @@ -445,6 +465,14 @@ def _run_cmake(self, board, origin, cmake_opts): if user_args: cmake_opts.extend(shlex.split(user_args)) + config_sysbuild = config_getboolean('sysbuild', False) + if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild): + cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR), + '-DAPP_DIR:PATH={}'.format(self.source_dir)]) + else: + # self.args.no_sysbuild == True or config sysbuild False + cmake_opts.extend(['-S{}'.format(self.source_dir)]) + # Invoke CMake from the current working directory using the # -S and -B options (officially introduced in CMake 3.13.0). # This is important because users expect invocations like this @@ -453,7 +481,6 @@ def _run_cmake(self, board, origin, cmake_opts): # west build -- -DOVERLAY_CONFIG=relative-path.conf final_cmake_args = ['-DWEST_PYTHON={}'.format(sys.executable), '-B{}'.format(self.build_dir), - '-S{}'.format(self.source_dir), '-G{}'.format(config_get('generator', DEFAULT_CMAKE_GENERATOR))] if cmake_opts: @@ -476,7 +503,7 @@ def _run_pristine(self): '-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake'] run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run) - def _run_build(self, target): + def _run_build(self, target, domain): if target: _banner('running target {}'.format(target)) elif self.run_cmake: @@ -488,8 +515,23 @@ def _run_build(self, target): if self.args.verbose: self._append_verbose_args(extra_args, not bool(self.args.build_opt)) - run_build(self.build_dir, extra_args=extra_args, - dry_run=self.args.dry_run) + + domains = load_domains(self.build_dir) + build_dir_list = [] + + if domain is None: + # If no domain is specified, we just build top build dir as that + # will build all domains. + build_dir_list = [domains.get_top_build_dir()] + else: + _banner('building domain(s): {}'.format(' '.join(domain))) + domain_list = domains.get_domains(domain) + for d in domain_list: + build_dir_list.append(d.build_dir) + + for b in build_dir_list: + run_build(b, extra_args=extra_args, + dry_run=self.args.dry_run) def _append_verbose_args(self, extra_args, add_dashes): # These hacks are only needed for CMake versions earlier than diff --git a/scripts/west_commands/build_helpers.py b/scripts/west_commands/build_helpers.py index a54978ffc315c..eca5bde6aeb9f 100644 --- a/scripts/west_commands/build_helpers.py +++ b/scripts/west_commands/build_helpers.py @@ -16,6 +16,7 @@ from west import log from west.configuration import config from west.util import escapes_directory +from domains import Domains DEFAULT_BUILD_DIR = 'build' '''Name of the default Zephyr build directory.''' @@ -133,3 +134,19 @@ def is_zephyr_build(path): log.dbg(f'{path} is NOT a valid zephyr build directory', level=log.VERBOSE_EXTREME) return False + + +def load_domains(path): + '''Load domains from a domains.yaml. + + If domains.yaml is not found, then a single 'app' domain referring to the + top-level build folder is created and returned. + ''' + domains_file = Path(path) / 'domains.yaml' + + if not domains_file.is_file(): + return Domains.from_data({'default': 'app', + 'build_dir': path, + 'domains': [{'name': 'app', 'build_dir': path}]}) + + return Domains.from_file(domains_file) diff --git a/scripts/west_commands/domains.py b/scripts/west_commands/domains.py new file mode 100644 index 0000000000000..9301c761f7210 --- /dev/null +++ b/scripts/west_commands/domains.py @@ -0,0 +1,139 @@ +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +'''Domain handling for west extension commands. + +This provides parsing of domains yaml file and creation of objects of the +Domain class. +''' + +import yaml +import pykwalify.core +from west import log + +DOMAINS_SCHEMA = ''' +## A pykwalify schema for basic validation of the structure of a +## domains YAML file. +## +# The domains.yaml file is a simple list of domains from a multi image build +# along with the default domain to use. +type: map +mapping: + default: + required: true + type: str + build_dir: + required: true + type: str + domains: + required: false + type: seq + sequence: + - type: map + mapping: + name: + required: true + type: str + build_dir: + required: true + type: str +''' + +schema = yaml.safe_load(DOMAINS_SCHEMA) + + +class Domains: + + def __init__(self, data): + self._domains = [] + self._domain_names = [] + self._domain_default = [] + + self._build_dir = data.get('build_dir') + domain_list = data.get('domains') + if not domain_list: + log.wrn("no domains defined; this probably won't work") + + for d in domain_list: + domain = Domain(d['name'], d['build_dir']) + self._domains.append(domain) + self._domain_names.append(domain.name) + if domain.name == data['default']: + self._default_domain = domain + + @staticmethod + def from_file(domains_file): + '''Load domains from domains.yaml. + + Exception raised: + - ``FileNotFoundError`` if the domains file is not found. + ''' + try: + with open(domains_file, 'r') as f: + domains = yaml.safe_load(f.read()) + except FileNotFoundError: + log.die(f'domains.yaml file not found: {domains_file}') + + try: + pykwalify.core.Core(source_data=domains, schema_data=schema)\ + .validate() + except pykwalify.errors.SchemaError: + log.die(f'ERROR: Malformed yaml in file: {domains_file}') + + return Domains(domains) + + @staticmethod + def from_data(domains_data): + '''Load domains from domains dictionary. + ''' + return Domains(domains_data) + + def get_domains(self, names=None): + ret = [] + + if not names: + return self._domains + + for n in names: + found = False + for d in self._domains: + if n == d.name: + ret.append(d) + found = True + break + # Getting here means the domain was not found. + # Todo: throw an error. + if not found: + log.die(f'domain {n} not found, ' + f'valid domains are:', *self._domain_names) + return ret + + def get_default_domain(self): + return self._default_domain + + def get_top_build_dir(self): + return self._build_dir + + +class Domain: + + def __init__(self, name, build_dir): + self.name = name + self.build_dir = build_dir + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def build_dir(self): + return self._build_dir + + @build_dir.setter + def build_dir(self, value): + self._build_dir = value diff --git a/scripts/west_commands/flash.py b/scripts/west_commands/flash.py index 321ba24bacd8e..073a1ab2a2838 100644 --- a/scripts/west_commands/flash.py +++ b/scripts/west_commands/flash.py @@ -8,7 +8,8 @@ from west.commands import WestCommand -from run_common import add_parser_common, do_run_common +from run_common import add_parser_common, do_run_common, get_build_dir +from build_helpers import load_domains class Flash(WestCommand): @@ -26,4 +27,6 @@ def do_add_parser(self, parser_adder): return add_parser_common(self, parser_adder) def do_run(self, my_args, runner_args): - do_run_common(self, my_args, runner_args) + build_dir = get_build_dir(my_args) + domains = load_domains(build_dir).get_domains(my_args.domain) + do_run_common(self, my_args, runner_args, domains=domains) diff --git a/scripts/west_commands/run_common.py b/scripts/west_commands/run_common.py index 84d3f0a208f72..2cde9e4c31846 100644 --- a/scripts/west_commands/run_common.py +++ b/scripts/west_commands/run_common.py @@ -16,7 +16,7 @@ import traceback from west import log -from build_helpers import find_build_dir, is_zephyr_build, \ +from build_helpers import find_build_dir, is_zephyr_build, load_domains, \ FIND_BUILD_DIR_DESCRIPTION from west.commands import CommandError from west.configuration import config @@ -104,6 +104,8 @@ def add_parser_common(command, parser_adder=None, parser=None): help='override default runner from --build-dir') group.add_argument('--skip-rebuild', action='store_true', help='do not refresh cmake dependencies first') + group.add_argument('--domain', action='append', + help='execute runner only for given domain') group = parser.add_argument_group( 'runner configuration', @@ -145,7 +147,7 @@ def add_parser_common(command, parser_adder=None, parser=None): return parser -def do_run_common(command, user_args, user_runner_args): +def do_run_common(command, user_args, user_runner_args, domains=None): # This is the main routine for all the "west flash", "west debug", # etc. commands. @@ -153,13 +155,30 @@ def do_run_common(command, user_args, user_runner_args): dump_context(command, user_args, user_runner_args) return - command_name = command.name build_dir = get_build_dir(user_args) - cache = load_cmake_cache(build_dir, user_args) - board = cache['CACHED_BOARD'] if not user_args.skip_rebuild: rebuild(command, build_dir, user_args) + if domains is None: + if user_args.domain is None: + # No domains are passed down and no domains specified by the user. + # So default domain will be used. + domains = [load_domains(build_dir).get_default_domain()] + else: + # No domains are passed down, but user has specified domains to use. + # Get the user specified domains. + domains = load_domains(build_dir).get_domains(user_args.domain) + + for d in domains: + do_run_common_image(command, user_args, user_runner_args, d.build_dir) + +def do_run_common_image(command, user_args, user_runner_args, build_dir=None): + command_name = command.name + if build_dir is None: + build_dir = get_build_dir(user_args) + cache = load_cmake_cache(build_dir, user_args) + board = cache['CACHED_BOARD'] + # Load runners.yaml. yaml_path = runners_yaml_path(build_dir, board) runners_yaml = load_runners_yaml(yaml_path) @@ -173,7 +192,9 @@ def do_run_common(command, user_args, user_runner_args): # Set up runner logging to delegate to west.log commands. logger = logging.getLogger('runners') logger.setLevel(LOG_LEVEL) - logger.addHandler(WestLogHandler()) + if not logger.hasHandlers(): + # Only add a runners log handler if none has been added already. + logger.addHandler(WestLogHandler()) # If the user passed -- to force the parent argument parser to stop # parsing, it will show up here, and needs to be filtered out. diff --git a/share/sysbuild/CMakeLists.txt b/share/sysbuild/CMakeLists.txt new file mode 100644 index 0000000000000..8b1042b48072f --- /dev/null +++ b/share/sysbuild/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20) + +if(NOT DEFINED APP_DIR) + message(FATAL_ERROR "No main application specified") +endif() + +# This will update the APP_DIR cache variable to PATH type and apply a comment. +# If APP_DIR is a relative path, then CMake will adjust to absolute path based +# on current working dir. +set(APP_DIR ${APP_DIR} CACHE PATH "Main Application Source Directory") + +# Add sysbuild/cmake/modules to CMAKE_MODULE_PATH which allows us to integrate +# sysbuild CMake modules with general Zephyr CMake modules. +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) +# List of Zephyr and sysbuild CMake modules we need for sysbuild. +# Note: sysbuild_kconfig will internally load kconfig CMake module. +set(zephyr_modules extensions sysbuild_extensions python west root zephyr_module boards shields sysbuild_kconfig) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} COMPONENTS ${zephyr_modules}) + +project(sysbuild LANGUAGES) + +# Global list of images enabled in this multi image build system. +set(IMAGES) + +get_filename_component(APP_DIR ${APP_DIR} ABSOLUTE) +get_filename_component(app_name ${APP_DIR} NAME) + +# Propagate bootloader and signing settings from this system to the MCUboot and +# application image build systems. +if(SB_CONFIG_BOOTLOADER_MCUBOOT) + set(${app_name}_CONFIG_BOOTLOADER_MCUBOOT y CACHE STRING + "MCUBOOT is enabled as bootloader" FORCE + ) + set(${app_name}_CONFIG_MCUBOOT_SIGNATURE_KEY_FILE + \"${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}\" CACHE STRING + "Signature key file for signing" FORCE + ) + + # Set corresponding values in mcuboot + set(mcuboot_CONFIG_BOOT_SIGNATURE_TYPE_${SB_CONFIG_SIGNATURE_TYPE} y CACHE STRING + "MCUBOOT signature type" FORCE + ) + set(mcuboot_CONFIG_BOOT_SIGNATURE_KEY_FILE + \"${SB_CONFIG_BOOT_SIGNATURE_KEY_FILE}\" CACHE STRING + "Signature key file for signing" FORCE + ) +else() + set(${app_name}_CONFIG_BOOTLOADER_MCUBOOT n CACHE STRING + "MCUBOOT is disabled as bootloader" FORCE + ) +endif() + +# This adds the primary application to the build. +ExternalZephyrProject_Add( + APPLICATION ${app_name} + SOURCE_DIR ${APP_DIR} + MAIN_APP +) +list(APPEND IMAGES "${app_name}") +set(DEFAULT_IMAGE "${app_name}") + +add_subdirectory(bootloader) + +# This allows for board and app specific images to be included. +include(${BOARD_DIR}/sysbuild.cmake OPTIONAL) +include(${APP_DIR}/sysbuild.cmake OPTIONAL) + +include(cmake/domains.cmake) diff --git a/share/sysbuild/Kconfig b/share/sysbuild/Kconfig new file mode 100644 index 0000000000000..0ef8f8b802351 --- /dev/null +++ b/share/sysbuild/Kconfig @@ -0,0 +1,36 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +comment "Sysbuild image configuration" + +osource "$(BOARD_DIR)/Kconfig.sysbuild" + +config EXPERIMENTAL + bool + help + Symbol that must be selected by a feature if it is considered to be + at an experimental implementation stage. + +config WARN_EXPERIMENTAL + bool + prompt "Warn on experimental usage" + help + Print a warning when the Kconfig tree is parsed if any experimental + features are enabled. + +config DEPRECATED + bool + help + Symbol that must be selected by a feature or module if it is + considered to be deprecated. + +config WARN_DEPRECATED + bool + default y + prompt "Warn on deprecated usage" + help + Print a warning when the Kconfig tree is parsed if any deprecated + features are enabled. + +rsource "bootloader/Kconfig" diff --git a/share/sysbuild/bootloader/CMakeLists.txt b/share/sysbuild/bootloader/CMakeLists.txt new file mode 100644 index 0000000000000..f9ba2ba2cb9f9 --- /dev/null +++ b/share/sysbuild/bootloader/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +# Include MCUboot if enabled. +if(SB_CONFIG_BOOTLOADER_MCUBOOT) + ExternalZephyrProject_Add( + APPLICATION mcuboot + SOURCE_DIR ${ZEPHYR_MCUBOOT_MODULE_DIR}/boot/zephyr/ + ) + # MCUBoot default configuration is to perform a full chip erase. + # Placing MCUBoot first in list to ensure it is flashed before other images. + set(IMAGES "mcuboot" ${IMAGES} PARENT_SCOPE) +endif() diff --git a/share/sysbuild/bootloader/Kconfig b/share/sysbuild/bootloader/Kconfig new file mode 100644 index 0000000000000..833e9e76469c4 --- /dev/null +++ b/share/sysbuild/bootloader/Kconfig @@ -0,0 +1,67 @@ +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +config SUPPORT_BOOTLOADER + bool + default y + +config SUPPORT_BOOTLOADER_MCUBOOT_ZEPHYR + bool + default y + +choice BOOTLOADER + prompt "Bootloader support" + default BOOTLOADER_NONE + depends on SUPPORT_BOOTLOADER + +config BOOTLOADER_NONE + bool "None" + help + Do not Include a bootloader in the build + +config BOOTLOADER_MCUBOOT + bool "MCUboot" + depends on SUPPORT_BOOTLOADER_MCUBOOT_ZEPHYR + help + Include MCUboot (Zephyr port) as the bootloader to use + +endchoice + +if BOOTLOADER_MCUBOOT + +config SIGNATURE_TYPE + string + default NONE if BOOT_SIGNATURE_TYPE_NONE + default RSA if BOOT_SIGNATURE_TYPE_RSA + default ECDSA_P256 if BOOT_SIGNATURE_TYPE_ECDSA_P256 + default ED25519 if BOOT_SIGNATURE_TYPE_ED25519 + +choice + prompt "Signature type" + default BOOT_SIGNATURE_TYPE_RSA + +config BOOT_SIGNATURE_TYPE_NONE + bool "No signature; use only hash check" + +config BOOT_SIGNATURE_TYPE_RSA + bool "RSA signatures" + +config BOOT_SIGNATURE_TYPE_ECDSA_P256 + bool "Elliptic curve digital signatures with curve P-256" + +config BOOT_SIGNATURE_TYPE_ED25519 + bool "Edwards curve digital signatures using ed25519" + +endchoice + +config BOOT_SIGNATURE_KEY_FILE + string "PEM key file" + default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-ec-p256.pem" if BOOT_SIGNATURE_TYPE_ECDSA_P256 + default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-ed25519.pem" if BOOT_SIGNATURE_TYPE_ED25519 + default "$(ZEPHYR_MCUBOOT_MODULE_DIR)/root-rsa-2048.pem" if BOOT_SIGNATURE_TYPE_RSA + default "" + help + Absolute path to key file to use with MCUBoot. + +endif diff --git a/share/sysbuild/cmake/domains.cmake b/share/sysbuild/cmake/domains.cmake new file mode 100644 index 0000000000000..68cbd8c63df90 --- /dev/null +++ b/share/sysbuild/cmake/domains.cmake @@ -0,0 +1,12 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +set(domains_yaml "default: ${DEFAULT_IMAGE}") +set(domains_yaml "${domains_yaml}\nbuild_dir: ${CMAKE_BINARY_DIR}") +set(domains_yaml "${domains_yaml}\ndomains:") +foreach(image ${IMAGES}) + set(domains_yaml "${domains_yaml}\n - name: ${image}") + set(domains_yaml "${domains_yaml}\n build_dir: $") +endforeach() +file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/domains.yaml CONTENT "${domains_yaml}") diff --git a/share/sysbuild/cmake/modules/sysbuild_extensions.cmake b/share/sysbuild/cmake/modules/sysbuild_extensions.cmake new file mode 100644 index 0000000000000..e9b841bf8c26b --- /dev/null +++ b/share/sysbuild/cmake/modules/sysbuild_extensions.cmake @@ -0,0 +1,187 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +# Usage: +# ExternalZephyrProject_Add(APPLICATION +# SOURCE_DIR +# [BOARD ] +# [MAIN_APP] +# ) +# +# This function includes a Zephyr based build system into the multiimage +# build system +# +# APPLICATION: : Name of the application, name will also be used for build +# folder of the application +# SOURCE_DIR : Source directory of the application +# BOARD : Use for application build instead user defined BOARD. +# MAIN_APP: Flag indicating this application is the main application +# and where user defined settings should be passed on as-is +# except for multi image build flags. +# For example, -DCONF_FILES= will be passed on to the +# MAIN_APP unmodified. +# +function(ExternalZephyrProject_Add) + cmake_parse_arguments(ZBUILD "MAIN_APP" "APPLICATION;BOARD;SOURCE_DIR" "" ${ARGN}) + + if(ZBUILD_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "ExternalZephyrProject_Add(${ARGV0} ...) given unknown arguments:" + " ${ZBUILD_UNPARSED_ARGUMENTS}" + ) + endif() + + set(sysbuild_vars + "APP_DIR" + "SB_CONF_FILE" + ) + + # General variables that should be propagated to all Zephyr builds, for example: + # - ZEPHYR_MODULES / ZEPHYR_EXTRA_MODULES + # - ZEPHYR_TOOLCHAIN_VARIANT + # - *_TOOLCHAIN_PATH + # - *_ROOT + # etc. + # Note: setting vars on a single image can be done by using + # `_CONF_FILE`, like `mcuboot_CONF_FILE` + set( + shared_image_variables_list + CMAKE_BUILD_TYPE + CMAKE_VERBOSE_MAKEFILE + BOARD + ZEPHYR_MODULES + ZEPHYR_EXTRA_MODULES + ZEPHYR_TOOLCHAIN_VARIANT + EXTRA_KCONFIG_TARGETS + ) + + set(shared_image_variables_regex + "^[^_]*_TOOLCHAIN_PATH|^[^_]*_ROOT" + ) + + set(app_cache_file ${CMAKE_BINARY_DIR}/CMake${ZBUILD_APPLICATION}PreloadCache.txt) + + if(EXISTS ${app_cache_file}) + file(STRINGS ${app_cache_file} app_cache_strings) + set(app_cache_strings_current ${app_cache_strings}) + endif() + + get_cmake_property(variables_cached CACHE_VARIABLES) + foreach(var_name ${variables_cached}) + # Any var of the form `_` should be propagated. + # For example mcuboot_= ==> -D= for mcuboot build. + if("${var_name}" MATCHES "^${ZBUILD_APPLICATION}_.*") + list(APPEND application_vars ${var_name}) + continue() + endif() + + # This means there is a match to another image than current one, ignore. + if("${var_name}" MATCHES "^.*_CONFIG_.*") + continue() + endif() + + # sysbuild reserved namespace. + if(var_name IN_LIST sysbuild_vars OR "${var_name}" MATCHES "^SB_CONFIG_.*") + continue() + endif() + + if("${var_name}" MATCHES "^CONFIG_.*") + if(ZBUILD_MAIN_APP) + list(APPEND application_vars ${var_name}) + endif() + continue() + endif() + + if(var_name IN_LIST shared_image_variables_list) + list(APPEND application_vars ${var_name}) + continue() + endif() + + if("${var_name}" MATCHES "${shared_image_variables_regex}") + list(APPEND application_vars ${var_name}) + endif() + endforeach() + + foreach(app_var_name ${application_vars}) + string(REGEX REPLACE "^${ZBUILD_APPLICATION}_" "" var_name "${app_var_name}") + get_property(var_type CACHE ${app_var_name} PROPERTY TYPE) + set(new_cache_entry "${var_name}:${var_type}=${${app_var_name}}") + if(NOT new_cache_entry IN_LIST app_cache_strings) + # This entry does not exists, let's see if it has been updated. + foreach(entry ${app_cache_strings}) + if("${entry}" MATCHES "^${var_name}:.*") + list(REMOVE_ITEM app_cache_strings "${entry}") + break() + endif() + endforeach() + list(APPEND app_cache_strings "${var_name}:${var_type}=${${app_var_name}}") + list(APPEND app_cache_entries "-D${var_name}:${var_type}=${${app_var_name}}") + endif() + endforeach() + + if(NOT "${app_cache_strings_current}" STREQUAL "${app_cache_strings}") + string(REPLACE ";" "\n" app_cache_strings "${app_cache_strings}") + file(WRITE ${app_cache_file} ${app_cache_strings}) + endif() + + if(DEFINED ZBUILD_BOARD) + list(APPEND app_cache_entries "-DBOARD=${ZBUILD_BOARD}") + elseif(NOT ZBUILD_MAIN_APP) + list(APPEND app_cache_entries "-DBOARD=${BOARD}") + endif() + + set(image_banner "* Running CMake for ${ZBUILD_APPLICATION} *") + string(LENGTH "${image_banner}" image_banner_width) + string(REPEAT "*" ${image_banner_width} image_banner_header) + message(STATUS "\n ${image_banner_header}\n" + " ${image_banner}\n" + " ${image_banner_header}\n" + ) + + execute_process( + COMMAND ${CMAKE_COMMAND} + -G${CMAKE_GENERATOR} + ${app_cache_entries} + -B${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + -S${ZBUILD_SOURCE_DIR} + RESULT_VARIABLE return_val + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + if(return_val) + message(FATAL_ERROR + "CMake configure failed for Zephyr project: ${ZBUILD_APPLICATION}\n" + "Location: ${ZBUILD_SOURCE_DIR}" + ) + endif() + + foreach(kconfig_target + menuconfig + hardenconfig + guiconfig + ${EXTRA_KCONFIG_TARGETS} + ) + + if(NOT ZBUILD_MAIN_APP) + set(image_prefix "${ZBUILD_APPLICATION}_") + endif() + + add_custom_target(${image_prefix}${kconfig_target} + ${CMAKE_MAKE_PROGRAM} ${kconfig_target} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + USES_TERMINAL + ) + endforeach() + include(ExternalProject) + ExternalProject_Add( + ${ZBUILD_APPLICATION} + SOURCE_DIR ${ZBUILD_SOURCE_DIR} + BINARY_DIR ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} --build . + INSTALL_COMMAND "" + BUILD_ALWAYS True + USES_TERMINAL_BUILD True + ) +endfunction() diff --git a/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake new file mode 100644 index 0000000000000..add0bb8ac0447 --- /dev/null +++ b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake @@ -0,0 +1,60 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_sysbuild_menuconfig + ${ZEPHYR_BASE}/scripts/kconfig/menuconfig.py +) + +set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_sysbuild_guiconfig + ${ZEPHYR_BASE}/scripts/kconfig/guiconfig.py +) + +set(KCONFIG_TARGETS sysbuild_menuconfig sysbuild_guiconfig) +list(TRANSFORM EXTRA_KCONFIG_TARGETS PREPEND "sysbuild_") + +if(DEFINED SB_CONF_FILE) + # SB_CONF_FILE already set so nothing to do. +elseif(DEFINED ENV{SB_CONF_FILE}) + set(SB_CONF_FILE $ENV{SB_CONF_FILE}) +elseif(EXISTS ${APP_DIR}/sysbuild.conf) + set(SB_CONF_FILE ${APP_DIR}/sysbuild.conf) +else() + # Because SYSBuild is opt-in feature, then it is permitted to not have a + # SYSBuild dedicated configuration file. +endif() + +if(DEFINED SB_CONF_FILE AND NOT IS_ABSOLUTE SB_CONF_FILE) + cmake_path(ABSOLUTE_PATH SB_CONF_FILE BASE_DIRECTORY ${APP_DIR} OUTPUT_VARIABLE SB_CONF_FILE) +endif() + +if(DEFINED SB_CONF_FILE AND NOT DEFINED CACHE{SB_CONF_FILE}) + # We only want to set this in cache it has been defined and is not already there. + set(SB_CONF_FILE ${SB_CONF_FILE} CACHE STRING "If desired, you can build the application with \ + SYSbuild configuration settings specified in an alternate .conf file using this parameter. \ + These settings will override the settings in the application’s SYSBuild config file or its \ + default .conf file. Multiple files may be listed, e.g. SB_CONF_FILE=\"sys1.conf sys2.conf\"") +endif() + +if(NOT DEFINED SB_CONF_FILE) + # If there is no SB_CONF_FILE, then use empty.conf to make kconfiglib happy. + # Not adding it to CMake cache ensures that a later created sysbuild.conf + # will be automatically detected. + set(SB_CONF_FILE ${CMAKE_CURRENT_BINARY_DIR}/empty.conf) +endif() + +# Empty files to make kconfig.py happy. +file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/empty.conf) +set(APPLICATION_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(KCONFIG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(AUTOCONF_H ${CMAKE_CURRENT_BINARY_DIR}/autoconf.h) +set(CONF_FILE ${SB_CONF_FILE}) +set(BOARD_DEFCONFIG "${CMAKE_CURRENT_BINARY_DIR}/empty.conf") +list(APPEND ZEPHYR_KCONFIG_MODULES_DIR BOARD=${BOARD}) +set(KCONFIG_NAMESPACE SB_CONFIG) + +if(EXISTS ${APP_DIR}/Kconfig.sysbuild) + set(KCONFIG_ROOT ${APP_DIR}/Kconfig.sysbuild) +endif() +include(${ZEPHYR_BASE}/cmake/modules/kconfig.cmake) +set(CONF_FILE)