Merge changes from topic "qemu_sel2" into integration

* changes:
  docs(build): describes the SPMC_OPTEE build option
  feat(qemu): support el3 spmc
  feat(el3-spmc): make platform logical partition optional
  feat(qemu): support s-el2 spmc
  feat(qemu): update abi between spmd and spmc
  fix(sptool): add dependency to SP image
This commit is contained in:
Olivier Deprez 2022-12-15 14:25:16 +01:00 committed by TrustedFirmware Code Review
commit 6d4f4c3e39
11 changed files with 331 additions and 58 deletions

View file

@ -818,6 +818,11 @@ Common build options
disabled). This configuration supports pre-Armv8.4 platforms (aka not
implementing the ``FEAT_SEL2`` extension). This is an experimental feature.
- ``SPMC_OPTEE`` : This boolean option is used jointly with the SPM
Dispatcher option (``SPD=spmd``) and with ``SPMD_SPM_AT_SEL2=0`` to
indicate that the SPMC at S-EL1 is OP-TEE and an OP-TEE specific loading
mechanism should be used.
- ``SPMD_SPM_AT_SEL2`` : This boolean option is used jointly with the SPM
Dispatcher option (``SPD=spmd``). When enabled (1) it indicates the SPMC
component runs at the S-EL2 exception level provided by the ``FEAT_SEL2``

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017-2021, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2017-2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@ -9,6 +9,18 @@
#include <common/desc_image_load.h>
#include <plat/common/platform.h>
#define SP_PKG_ENTRY(id) \
{ \
.image_id = (id), \
SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY, VERSION_2, \
entry_point_info_t, \
SECURE | NON_EXECUTABLE), \
SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY, \
VERSION_2, image_info_t, \
IMAGE_ATTRIB_SKIP_LOADING), \
.next_handoff_image_id = INVALID_IMAGE_ID, \
}
/*******************************************************************************
* Following descriptor provides BL image/ep information that gets used
* by BL2 to load the images and also subset of this information is
@ -122,6 +134,48 @@ static bl_mem_params_node_t bl2_mem_params_descs[] = {
#endif
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#if defined(SPD_spmd)
/* Fill TOS_FW_CONFIG related information */
{
.image_id = TOS_FW_CONFIG_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY,
VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE),
SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY,
VERSION_2, image_info_t, 0),
.image_info.image_base = TOS_FW_CONFIG_BASE,
.image_info.image_max_size = TOS_FW_CONFIG_LIMIT -
TOS_FW_CONFIG_BASE,
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#if SPMD_SPM_AT_SEL2
/* Fill TB_FW_CONFIG related information */
{
.image_id = TB_FW_CONFIG_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY,
VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE),
SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY,
VERSION_2, image_info_t, 0),
.image_info.image_base = TB_FW_CONFIG_BASE,
.image_info.image_max_size = TB_FW_CONFIG_LIMIT - TB_FW_CONFIG_BASE,
.next_handoff_image_id = INVALID_IMAGE_ID,
},
/*
* Empty entries for SP packages to be filled in according to
* TB_FW_CONFIG.
*/
SP_PKG_ENTRY(SP_PKG1_ID),
SP_PKG_ENTRY(SP_PKG2_ID),
SP_PKG_ENTRY(SP_PKG3_ID),
SP_PKG_ENTRY(SP_PKG4_ID),
SP_PKG_ENTRY(SP_PKG5_ID),
SP_PKG_ENTRY(SP_PKG6_ID),
SP_PKG_ENTRY(SP_PKG7_ID),
SP_PKG_ENTRY(SP_PKG8_ID),
#endif
#endif
# endif /* QEMU_LOAD_BL32 */
/* Fill BL33 related information */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2021, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2015-2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@ -16,6 +16,7 @@
#include <common/debug.h>
#include <common/desc_image_load.h>
#include <common/fdt_fixup.h>
#include <common/fdt_wrappers.h>
#include <lib/optee_utils.h>
#include <lib/utils.h>
#include <plat/common/platform.h>
@ -140,6 +141,48 @@ static uint32_t qemu_get_spsr_for_bl33_entry(void)
return spsr;
}
#if defined(SPD_spmd) && SPMD_SPM_AT_SEL2
static int load_sps_from_tb_fw_config(struct image_info *image_info)
{
void *dtb = (void *)image_info->image_base;
const char *compat_str = "arm,sp";
const struct fdt_property *uuid;
uint32_t load_addr;
const char *name;
int sp_node;
int node;
node = fdt_node_offset_by_compatible(dtb, -1, compat_str);
if (node < 0) {
ERROR("Can't find %s in TB_FW_CONFIG", compat_str);
return -1;
}
fdt_for_each_subnode(sp_node, dtb, node) {
name = fdt_get_name(dtb, sp_node, NULL);
if (name == NULL) {
ERROR("Can't get name of node in dtb\n");
return -1;
}
uuid = fdt_get_property(dtb, sp_node, "uuid", NULL);
if (uuid == NULL) {
ERROR("Can't find property uuid in node %s", name);
return -1;
}
if (fdt_read_uint32(dtb, sp_node, "load-address",
&load_addr) < 0) {
ERROR("Can't read load-address in node %s", name);
return -1;
}
if (qemu_io_register_sp_pkg(name, uuid->data, load_addr) < 0) {
return -1;
}
}
return 0;
}
#endif /*defined(SPD_spmd) && SPMD_SPM_AT_SEL2*/
static int qemu_bl2_handle_post_image_load(unsigned int image_id)
{
int err = 0;
@ -149,8 +192,7 @@ static int qemu_bl2_handle_post_image_load(unsigned int image_id)
bl_mem_params_node_t *paged_mem_params = NULL;
#endif
#if defined(SPD_spmd)
unsigned int mode_rw = MODE_RW_64;
uint64_t pagable_part = 0;
bl_mem_params_node_t *bl32_mem_params = NULL;
#endif
assert(bl_mem_params);
@ -170,17 +212,18 @@ static int qemu_bl2_handle_post_image_load(unsigned int image_id)
if (err != 0) {
WARN("OPTEE header parse error.\n");
}
#if defined(SPD_spmd)
mode_rw = bl_mem_params->ep_info.args.arg0;
pagable_part = bl_mem_params->ep_info.args.arg1;
#endif
#endif
#if defined(SPD_spmd)
bl_mem_params->ep_info.args.arg0 = ARM_PRELOADED_DTB_BASE;
bl_mem_params->ep_info.args.arg1 = pagable_part;
bl_mem_params->ep_info.args.arg2 = mode_rw;
bl_mem_params->ep_info.args.arg3 = 0;
#if defined(SPMC_OPTEE)
/*
* Explicit zeroes to unused registers since they may have
* been populated by parse_optee_header() above.
*
* OP-TEE expects system DTB in x2 and TOS_FW_CONFIG in x0,
* the latter is filled in below for TOS_FW_CONFIG_ID and
* applies to any other SPMC too.
*/
bl_mem_params->ep_info.args.arg2 = ARM_PRELOADED_DTB_BASE;
#elif defined(SPD_opteed)
/*
* OP-TEE expect to receive DTB address in x2.
@ -224,6 +267,19 @@ static int qemu_bl2_handle_post_image_load(unsigned int image_id)
bl_mem_params->ep_info.spsr = qemu_get_spsr_for_bl33_entry();
break;
#ifdef SPD_spmd
#if SPMD_SPM_AT_SEL2
case TB_FW_CONFIG_ID:
err = load_sps_from_tb_fw_config(&bl_mem_params->image_info);
break;
#endif
case TOS_FW_CONFIG_ID:
/* An SPMC expects TOS_FW_CONFIG in x0/r0 */
bl32_mem_params = get_bl_mem_params_node(BL32_IMAGE_ID);
bl32_mem_params->ep_info.args.arg0 =
bl_mem_params->image_info.image_base;
break;
#endif
default:
/* Do nothing in default case */
break;

View file

@ -10,6 +10,7 @@
#include <arch_helpers.h>
#include <common/bl_common.h>
#include <lib/xlat_tables/xlat_tables_v2.h>
#include <services/el3_spmc_ffa_memory.h>
#include <plat/common/platform.h>
#include "qemu_private.h"
@ -100,7 +101,7 @@ static const mmap_region_t plat_qemu_mmap[] = {
#if SPM_MM
MAP_NS_DRAM0,
QEMU_SPM_BUF_EL3_MMAP,
#else
#elif !SPMC_AT_EL3
MAP_BL32_MEM,
#endif
{0}
@ -167,3 +168,30 @@ int plat_get_mbedtls_heap(void **heap_addr, size_t *heap_size)
return get_mbedtls_heap_helper(heap_addr, heap_size);
}
#endif
#if SPMC_AT_EL3
/*
* When using the EL3 SPMC implementation allocate the datastore
* for tracking shared memory descriptors in normal memory.
*/
#define PLAT_SPMC_SHMEM_DATASTORE_SIZE 64 * 1024
uint8_t plat_spmc_shmem_datastore[PLAT_SPMC_SHMEM_DATASTORE_SIZE];
int plat_spmc_shmem_datastore_get(uint8_t **datastore, size_t *size)
{
*datastore = plat_spmc_shmem_datastore;
*size = PLAT_SPMC_SHMEM_DATASTORE_SIZE;
return 0;
}
int plat_spmc_shmem_begin(struct ffa_mtd *desc)
{
return 0;
}
int plat_spmc_shmem_reclaim(struct ffa_mtd *desc)
{
return 0;
}
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2016, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2015-2022, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@ -11,6 +11,8 @@
#include <common/bl_common.h>
#include <common/debug.h>
#include <common/desc_image_load.h>
#include <common/uuid.h>
#include <drivers/io/io_driver.h>
#include <drivers/io/io_encrypted.h>
#include <drivers/io/io_fip.h>
@ -20,10 +22,14 @@
#include <lib/semihosting.h>
#include <tools_share/firmware_image_package.h>
#include "qemu_private.h"
/* Semihosting filenames */
#define BL2_IMAGE_NAME "bl2.bin"
#define BL31_IMAGE_NAME "bl31.bin"
#define BL32_IMAGE_NAME "bl32.bin"
#define TB_FW_CONFIG_NAME "tb_fw_config.dtb"
#define TOS_FW_CONFIG_NAME "tos_fw_config.dtb"
#define BL32_EXTRA1_IMAGE_NAME "bl32_extra1.bin"
#define BL32_EXTRA2_IMAGE_NAME "bl32_extra2.bin"
#define BL33_IMAGE_NAME "bl33.bin"
@ -78,6 +84,14 @@ static const io_uuid_spec_t bl32_extra2_uuid_spec = {
.uuid = UUID_SECURE_PAYLOAD_BL32_EXTRA2,
};
static const io_uuid_spec_t tb_fw_config_uuid_spec = {
.uuid = UUID_TB_FW_CONFIG,
};
static const io_uuid_spec_t tos_fw_config_uuid_spec = {
.uuid = UUID_TOS_FW_CONFIG,
};
static const io_uuid_spec_t bl33_uuid_spec = {
.uuid = UUID_NON_TRUSTED_FIRMWARE_BL33,
};
@ -137,6 +151,14 @@ static const io_file_spec_t sh_file_spec[] = {
.path = BL32_EXTRA2_IMAGE_NAME,
.mode = FOPEN_MODE_RB
},
[TB_FW_CONFIG_ID] = {
.path = TB_FW_CONFIG_NAME,
.mode = FOPEN_MODE_RB
},
[TOS_FW_CONFIG_ID] = {
.path = TOS_FW_CONFIG_NAME,
.mode = FOPEN_MODE_RB
},
[BL33_IMAGE_ID] = {
.path = BL33_IMAGE_NAME,
.mode = FOPEN_MODE_RB
@ -252,6 +274,16 @@ static const struct plat_io_policy policies[] = {
open_fip
},
#endif
[TB_FW_CONFIG_ID] = {
&fip_dev_handle,
(uintptr_t)&tb_fw_config_uuid_spec,
open_fip
},
[TOS_FW_CONFIG_ID] = {
&fip_dev_handle,
(uintptr_t)&tos_fw_config_uuid_spec,
open_fip
},
[BL33_IMAGE_ID] = {
&fip_dev_handle,
(uintptr_t)&bl33_uuid_spec,
@ -301,6 +333,80 @@ static const struct plat_io_policy policies[] = {
#endif /* TRUSTED_BOARD_BOOT */
};
#if defined(SPD_spmd)
static struct sp_pkg {
struct plat_io_policy policy;
io_file_spec_t sh_file_spec;
uint8_t uuid[UUID_BYTES_LENGTH];
char path[80];
} sp_pkgs[MAX_SP_IDS];
static unsigned int sp_pkg_count;
int qemu_io_register_sp_pkg(const char *name, const char *uuid,
uintptr_t load_addr)
{
struct sp_pkg *pkg;
bl_mem_params_node_t *mem_params;
if (sp_pkg_count == MAX_SP_IDS) {
INFO("Reached Max number of SPs\n");
return -1;
}
mem_params = get_bl_mem_params_node(SP_PKG1_ID + sp_pkg_count);
if (mem_params == NULL) {
ERROR("Can't find SP_PKG ID %u (SP_PKG%u_ID)\n",
SP_PKG1_ID + sp_pkg_count, sp_pkg_count);
return -1;
}
pkg = sp_pkgs + sp_pkg_count;
if (read_uuid(pkg->uuid, (char *)uuid)) {
return -1;
}
strlcpy(pkg->path, name, sizeof(pkg->path));
strlcat(pkg->path, ".pkg", sizeof(pkg->path));
pkg->policy.dev_handle = &fip_dev_handle;
pkg->policy.image_spec = (uintptr_t)&pkg->uuid;
pkg->policy.check = open_fip;
pkg->sh_file_spec.path = pkg->path;
pkg->sh_file_spec.mode = FOPEN_MODE_RB;
mem_params->image_info.image_base = load_addr;
mem_params->image_info.image_max_size = SZ_4M;
mem_params->image_info.h.attr &= ~IMAGE_ATTRIB_SKIP_LOADING;
sp_pkg_count++;
return 0;
}
#endif /*SPD_spmd*/
static const io_file_spec_t *get_io_file_spec(unsigned int image_id)
{
#if defined(SPD_spmd)
if (image_id >= SP_PKG1_ID && image_id <= SP_PKG8_ID) {
return &sp_pkgs[image_id - SP_PKG1_ID].sh_file_spec;
}
#endif
assert(image_id < ARRAY_SIZE(sh_file_spec));
return &sh_file_spec[image_id];
}
static const struct plat_io_policy *get_io_policy(unsigned int image_id)
{
#if defined(SPD_spmd)
if (image_id >= SP_PKG1_ID && image_id <= SP_PKG8_ID) {
return &sp_pkgs[image_id - SP_PKG1_ID].policy;
}
#endif
assert(image_id < ARRAY_SIZE(policies));
return &policies[image_id];
}
static int open_fip(const uintptr_t spec)
{
int result;
@ -413,11 +519,13 @@ void plat_qemu_io_setup(void)
static int get_alt_image_source(unsigned int image_id, uintptr_t *dev_handle,
uintptr_t *image_spec)
{
int result = open_semihosting((const uintptr_t)&sh_file_spec[image_id]);
const io_file_spec_t *spec = get_io_file_spec(image_id);
int result;
result = open_semihosting((const uintptr_t)spec);
if (result == 0) {
*dev_handle = sh_dev_handle;
*image_spec = (uintptr_t)&sh_file_spec[image_id];
*image_spec = (uintptr_t)spec;
}
return result;
@ -430,12 +538,9 @@ static int get_alt_image_source(unsigned int image_id, uintptr_t *dev_handle,
int plat_get_image_source(unsigned int image_id, uintptr_t *dev_handle,
uintptr_t *image_spec)
{
const struct plat_io_policy *policy = get_io_policy(image_id);
int result;
const struct plat_io_policy *policy;
assert(image_id < ARRAY_SIZE(policies));
policy = &policies[image_id];
result = policy->check(policy->image_spec);
if (result == 0) {
*image_spec = policy->image_spec;

View file

@ -26,6 +26,8 @@ void qemu_configure_mmu_el3(unsigned long total_base, unsigned long total_size,
unsigned long coh_start, unsigned long coh_limit);
void plat_qemu_io_setup(void);
int qemu_io_register_sp_pkg(const char *name, const char *uuid,
uintptr_t load_addr);
unsigned int plat_qemu_calc_core_pos(u_register_t mpidr);
void qemu_console_init(void);

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2021, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <services/spm_core_manifest.h>
#include <plat/common/platform.h>
#include <platform_def.h>
int plat_spm_core_manifest_load(spmc_manifest_attribute_t *manifest,
const void *pm_addr)
{
entry_point_info_t *ep_info = bl31_plat_get_next_image_ep_info(SECURE);
assert(ep_info != NULL);
assert(manifest != NULL);
manifest->major_version = 1;
manifest->minor_version = 0;
manifest->exec_state = ep_info->args.arg2;
manifest->load_address = BL32_BASE;
manifest->entrypoint = BL32_BASE;
manifest->binary_size = BL32_LIMIT - BL32_BASE;
manifest->spmc_id = 0x8000;
return 0;
}

View file

@ -118,6 +118,11 @@
#define BL_RAM_BASE (SHARED_RAM_BASE + SHARED_RAM_SIZE)
#define BL_RAM_SIZE (SEC_SRAM_SIZE - SHARED_RAM_SIZE)
#define TB_FW_CONFIG_BASE BL_RAM_BASE
#define TB_FW_CONFIG_LIMIT (TB_FW_CONFIG_BASE + PAGE_SIZE)
#define TOS_FW_CONFIG_BASE TB_FW_CONFIG_LIMIT
#define TOS_FW_CONFIG_LIMIT (TOS_FW_CONFIG_BASE + PAGE_SIZE)
/*
* BL1 specific defines.
*
@ -183,8 +188,8 @@
#define PLAT_PHY_ADDR_SPACE_SIZE (1ULL << 32)
#define PLAT_VIRT_ADDR_SPACE_SIZE (1ULL << 32)
#define MAX_MMAP_REGIONS 11
#define MAX_XLAT_TABLES 6
#define MAX_MMAP_REGIONS (11 + MAX_MMAP_REGIONS_SPMC)
#define MAX_XLAT_TABLES (6 + MAX_XLAT_TABLES_SPMC)
#define MAX_IO_DEVICES 4
#define MAX_IO_HANDLES 4
@ -275,4 +280,32 @@
*/
#define PLAT_EVENT_LOG_MAX_SIZE UL(0x400)
#if SPMC_AT_EL3
/*
* Number of Secure Partitions supported.
* SPMC at EL3, uses this count to configure the maximum number of
* supported secure partitions.
*/
#define SECURE_PARTITION_COUNT 1
/*
* Number of Logical Partitions supported.
* SPMC at EL3, uses this count to configure the maximum number of
* supported logical partitions.
*/
#define MAX_EL3_LP_DESCS_COUNT 0
/*
* Number of Normal World Partitions supported.
* SPMC at EL3, uses this count to configure the maximum number of
* supported normal world partitions.
*/
#define NS_PARTITION_COUNT 1
#define MAX_MMAP_REGIONS_SPMC 2
#define MAX_XLAT_TABLES_SPMC 4
#else
#define MAX_MMAP_REGIONS_SPMC 0
#define MAX_XLAT_TABLES_SPMC 0
#endif
#endif /* PLATFORM_DEF_H */

View file

@ -161,7 +161,8 @@ BL2_SOURCES += drivers/io/io_semihosting.c \
${PLAT_QEMU_COMMON_PATH}/qemu_image_load.c \
common/fdt_fixup.c \
common/fdt_wrappers.c \
common/desc_image_load.c
common/desc_image_load.c \
common/uuid.c
ifeq ($(add-lib-optee),yes)
BL2_SOURCES += lib/optee/optee_utils.c
@ -217,7 +218,10 @@ PLAT_BL_COMMON_SOURCES += plat/arm/common/aarch64/arm_pauth.c \
endif
ifeq (${SPD},spmd)
BL31_SOURCES += plat/qemu/common/qemu_spmd_manifest.c
BL31_SOURCES += plat/common/plat_spmd_manifest.c \
common/uuid.c \
${LIBFDT_SRCS} \
${FDT_WRAPPERS_SOURCES}
endif
endif
@ -238,6 +242,20 @@ $(eval $(call TOOL_ADD_IMG,bl32_extra2,--tos-fw-extra2))
endif
endif
ifneq ($(QEMU_TB_FW_CONFIG_DTS),)
FDT_SOURCES += ${QEMU_TB_FW_CONFIG_DTS}
QEMU_TB_FW_CONFIG := ${BUILD_PLAT}/fdts/$(notdir $(basename ${QEMU_TB_FW_CONFIG_DTS})).dtb
# Add the TB_FW_CONFIG to FIP
$(eval $(call TOOL_ADD_PAYLOAD,${QEMU_TB_FW_CONFIG},--tb-fw-config,${QEMU_TB_FW_CONFIG}))
endif
ifneq ($(QEMU_TOS_FW_CONFIG_DTS),)
FDT_SOURCES += ${QEMU_TOS_FW_CONFIG_DTS}
QEMU_TOS_FW_CONFIG := ${BUILD_PLAT}/fdts/$(notdir $(basename ${QEMU_TOS_FW_CONFIG_DTS})).dtb
# Add the TOS_FW_CONFIG to FIP
$(eval $(call TOOL_ADD_PAYLOAD,${QEMU_TOS_FW_CONFIG},--tos-fw-config,${QEMU_TOS_FW_CONFIG}))
endif
SEPARATE_CODE_AND_RODATA := 1
ENABLE_STACK_PROTECTOR := 0
ifneq ($(ENABLE_STACK_PROTECTOR), 0)

View file

@ -20,7 +20,9 @@ SPMC_LP_SOURCES := $(addprefix ${PLAT_DIR}/, \
${PLAT}_el3_spmc_logical_sp.c)
ifneq ($(wildcard $(SPMC_LP_SOURCES)),)
SPMC_SOURCES += $(SPMC_LP_SOURCES)
endif
# Let the top-level Makefile know that we intend to include a BL32 image
NEED_BL32 := yes

View file

@ -132,20 +132,21 @@ def gen_sptool_args(sp_layout, sp, args :dict):
sp_pkg = get_sp_pkg(sp, args)
sp_dtb_name = os.path.basename(get_file_from_layout(sp_layout[sp]["pm"]))[:-1] + "b"
sp_dtb = os.path.join(args["out_dir"], f"fdts/{sp_dtb_name}")
sp_img = get_sp_img_full_path(sp_layout[sp], args)
# Do not generate rule if already there.
if is_line_in_sp_gen(f'{sp_pkg}:', args):
return args
write_to_sp_mk_gen(f"SP_PKGS += {sp_pkg}\n", args)
sptool_args = f" -i {get_sp_img_full_path(sp_layout[sp], args)}:{sp_dtb}"
sptool_args = f" -i {sp_img}:{sp_dtb}"
pm_offset = get_pm_offset(sp_layout[sp])
sptool_args += f" --pm-offset {pm_offset}" if pm_offset is not None else ""
image_offset = get_image_offset(sp_layout[sp])
sptool_args += f" --img-offset {image_offset}" if image_offset is not None else ""
sptool_args += f" -o {sp_pkg}"
sppkg_rule = f'''
{sp_pkg}: {sp_dtb}
{sp_pkg}: {sp_dtb} {sp_img}
\t$(Q)echo Generating {sp_pkg}
\t$(Q)$(PYTHON) $(SPTOOL) {sptool_args}
'''