On 8/28/2026 7:27 PM, Richard Genoud (TI) wrote:
> From: Prasanth Babu Mantena <[email protected]>
>
> Add helper functions that are used by respective SoCs in LPM resume flow.
>
> - lpm_process() is called at boot time to:
> - retrieve the LPM memory region from DTS
> - save ATF/OPTEE certificates information and DM code in this memory
> region
> - Forward the LPM address to TIFS via TISCI_MSG_LPM_SAVE_ADDR
> TIFS will use this address to save TFA context and its own minimal
> context just before suspend.
>
> - do_resume() is called at resume, just after bringing the DDR out of
> retention to:
> - retrieve the LPM memory region from DTS
> - authenticate certificates from LPM memory region and apply firewalls
> - ask TIFS to restore TFA and its own minimal context
> - start TFA on remote proc
> - load and jump to DM
>
> - k3_set_lpm_core_bus_node_path() and k3_set_lpm_core_compatible() are
> used to locate the lpm memory region in the DTB
>
> https://software-dl.ti.com/tisci/esd/latest/2_tisci_msgs/pm/lpm.html#lpm-msg-lpm-save-addr
>
> Signed-off-by: Prasanth Babu Mantena <[email protected]>
> Co-developed-by: Richard Genoud (TI) <[email protected]>
> Signed-off-by: Richard Genoud (TI) <[email protected]>
> ---
> arch/arm/mach-k3/common.h | 16 ++
> arch/arm/mach-k3/lpm-common.h | 17 ++
> arch/arm/mach-k3/r5/Kconfig | 4 +
> arch/arm/mach-k3/r5/Makefile | 1 +
> arch/arm/mach-k3/r5/common.c | 17 +-
> arch/arm/mach-k3/r5/lpm-common.c | 268 +++++++++++++++++++++++++++++++
> 6 files changed, 311 insertions(+), 12 deletions(-)
> create mode 100644 arch/arm/mach-k3/lpm-common.h
> create mode 100644 arch/arm/mach-k3/r5/lpm-common.c
>
> diff --git a/arch/arm/mach-k3/common.h b/arch/arm/mach-k3/common.h
> index 37ff98d89924..c97ccc8e0619 100644
> --- a/arch/arm/mach-k3/common.h
> +++ b/arch/arm/mach-k3/common.h
> @@ -8,6 +8,7 @@
>
> #include <asm/armv7_mpu.h>
> #include <asm/hardware.h>
> +#include <image.h>
> #include <mach/security.h>
>
> /* keep ram_top in the 32-bit address space */
> @@ -16,6 +17,21 @@
> #define K3_FIREWALL_BACKGROUND_BIT (8)
> #define K3_SPEED_GRADE_UNKNOWN '\0'
>
> +#if IS_ENABLED(CONFIG_SYS_K3_SPL_ATF)
> +enum {
> + IMAGE_ID_ATF,
> + IMAGE_ID_OPTEE,
> + IMAGE_ID_SPL,
> + IMAGE_ID_DM_FW,
> + IMAGE_ID_TIFSSTUB_HS,
> + IMAGE_ID_TIFSSTUB_FS,
> + IMAGE_ID_TIFSSTUB_GP,
> + IMAGE_AMT,
> +};
> +
> +extern struct image_info fit_image_info[IMAGE_AMT];
> +#endif
> +
> struct fwl_data {
> const char *name;
> u16 fwl_id;
> diff --git a/arch/arm/mach-k3/lpm-common.h b/arch/arm/mach-k3/lpm-common.h
> new file mode 100644
> index 000000000000..83e0fdcca6ca
> --- /dev/null
> +++ b/arch/arm/mach-k3/lpm-common.h
> @@ -0,0 +1,17 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * K3: LPM Architecture common definitions
> + *
> + * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
> + * Copyright (C) 2026 Bootlin
> + */
> +
> +#ifndef _LPM_COMMON_H_
> +#define _LPM_COMMON_H_
> +
> +void __noreturn do_resume(void);
> +void lpm_process(void);
> +void k3_set_lpm_core_bus_node_path(const char *node_path);
> +void k3_set_lpm_core_compatible(const char *compatible);
> +
> +#endif
> diff --git a/arch/arm/mach-k3/r5/Kconfig b/arch/arm/mach-k3/r5/Kconfig
> index 12335880e106..7a9c005a6d7c 100644
> --- a/arch/arm/mach-k3/r5/Kconfig
> +++ b/arch/arm/mach-k3/r5/Kconfig
> @@ -1,6 +1,10 @@
> config K3_LOAD_SYSFW
> bool
>
> +config K3_LPM
> + bool
> + default y if SOC_K3_J721E || SOC_K3_J7200 || SOC_K3_J784S4 ||
> SOC_K3_J722S || SOC_K3_J721S2
SOC_K3_J721E does not support LPM
> +
> config K3_OPP_LOW
> depends on ARCH_K3 && K3_AVS0
> bool "Enable OPP_LOW on supported TI K3 SoCs"
> diff --git a/arch/arm/mach-k3/r5/Makefile b/arch/arm/mach-k3/r5/Makefile
> index 074e3b61a262..ccededbc0d60 100644
> --- a/arch/arm/mach-k3/r5/Makefile
> +++ b/arch/arm/mach-k3/r5/Makefile
> @@ -13,6 +13,7 @@ obj-$(CONFIG_SOC_K3_J722S) += j722s/
> obj-$(CONFIG_SOC_K3_J784S4) += j784s4/
>
> obj-y += common.o
> +obj-$(CONFIG_K3_LPM) += lpm-common.o
> obj-y += lowlevel_init.o
> obj-y += r5_mpu.o
>
> [..]
> +static int extract_lpm_region(struct lpm_addr_info *mem_addr_lpm)
> +{
> + struct ofnode_phandle_args memregion_phandle;
> + fdt_addr_t lpm_reg_addr;
> + fdt_size_t lpm_reg_size;
> + ofnode memregion;
> + ofnode bus_node;
> + int ret, idx;
> +
> + if (!lpm_core_compatible) {
> + printf("LPM compatible core string not set\n");
> + return -EINVAL;
> + }
> + if (!lpm_core_bus_node_path) {
> + printf("LPM core bus path not set\n");
> + return -EINVAL;
> + }
> +
> + bus_node = ofnode_path(lpm_core_bus_node_path);
> + if (!ofnode_valid(bus_node)) {
> + printf("Failed to find wkup bus\n");
> + return -EINVAL;
> + }
> +
> + memregion = ofnode_by_compatible(bus_node, lpm_core_compatible);
> + if (!ofnode_valid(memregion)) {
> + printf("Failed to find r5f devicetree node %s\n",
> + lpm_core_compatible);
> + return -EINVAL;
> + }
> +
> + idx = ofnode_stringlist_search(memregion, "memory-region-names",
> + "lpm-metadata");
> + if (idx < 0)
> + return idx;
> +
> + ret = ofnode_parse_phandle_with_args(memregion, "memory-region", NULL,
> + 0, idx, &memregion_phandle);
> +
> + if (ret) {
> + printf("Failed to parse phandle for lpm memory region\n");
> + return ret;
> + }
> +
> + lpm_reg_addr = ofnode_get_addr(memregion_phandle.node);
> + if (lpm_reg_addr == FDT_ADDR_T_NONE) {
> + printf("Can't find a valid reserved node!\n");
> + return -ENODEV;
> + }
> +
> + lpm_reg_size = ofnode_get_size(memregion_phandle.node);
> + if (lpm_reg_size == FDT_ADDR_T_NONE) {
> + printf("Can't find a valid reserved node!\n");
> + return -ENODEV;
> + }
> +
having check on lpm_reg_size will be helpful, so that you are not
exceeding below allocation of memory.
> + mem_addr_lpm->context_save_addr = lpm_reg_addr;
> + mem_addr_lpm->atf_cert_addr = mem_addr_lpm->context_save_addr +
> FW_IMAGE_SIZE;
> + mem_addr_lpm->optee_cert_addr = mem_addr_lpm->atf_cert_addr +
> FW_IMAGE_SIZE;
> + mem_addr_lpm->dm_save_addr = mem_addr_lpm->optee_cert_addr + (2 *
> FW_IMAGE_SIZE);
> + mem_addr_lpm->size = lpm_reg_size;
> +
> + return 0;
> +}
> +
> +static int save_certificate(struct lpm_addr_info *mem_addr_lpm)
Since we are saving images as well, so save_certificate is not right
function name.
> +{
> + int ret;
> +
> + if (!fit_image_info[IMAGE_ID_ATF].image_start ||
> + !fit_image_info[IMAGE_ID_OPTEE].image_start ||
> + !fit_image_info[IMAGE_ID_DM_FW].image_start) {
> + pr_err("Invalid images to save\n");
> + return -EINVAL;
> + }
> +
> + ret = extract_lpm_region(mem_addr_lpm);
> + if (ret) {
> + pr_err("Cannot find valid LPM address range..\n");
> + return -ENOMEM;
> + }
> +
> + memcpy((void *)mem_addr_lpm->atf_cert_addr,
> + (void *)fit_image_info[IMAGE_ID_ATF].image_start,
> + fit_image_info[IMAGE_ID_ATF].image_len);
> +
> + memcpy((void *)mem_addr_lpm->optee_cert_addr,
> + (void *)fit_image_info[IMAGE_ID_OPTEE].image_start,
> + fit_image_info[IMAGE_ID_OPTEE].image_len);
> +
> + memcpy((void *)mem_addr_lpm->dm_save_addr,
> + (void *)fit_image_info[IMAGE_ID_DM_FW].image_start,
> + fit_image_info[IMAGE_ID_DM_FW].image_len);
Is this DM image with certificates ?
> +
> + return 0;
> +}
> +
> +void lpm_process(void)
> +{
> + int ret = 0;
> + struct lpm_addr_info mem_addr_lpm;
> + struct ti_sci_handle *ti_sci = get_ti_sci_handle();
> +
> + ret = save_certificate(&mem_addr_lpm);
> + if (ret)
> + return;
> + /*
> + * As there is no function to check TIFS capabilities, we can't really
> + * know if the call failed because it's not supported by TIFS or for
> + * another reason.
> + */
> + ret = ti_sci->ops.lpm_ops.lpm_save_addr(ti_sci,
> + mem_addr_lpm.context_save_addr,
> + mem_addr_lpm.size);
> + if (ret)
> + pr_err("TIFS lpm save addr fails (message not supported?)\n");
> +}
> +
> +static unsigned long resume_to_dm_f(const struct lpm_addr_info *mem_addr_lpm)
> +{
> + struct ti_sci_handle *ti_sci = get_ti_sci_handle();
> + unsigned long loadaddr;
> + int ret = 0;
> +
> + loadaddr = mem_addr_lpm->dm_save_addr;
> + if (!valid_elf_image(loadaddr))
> + panic("%s: DM-Firmware image is not valid, it cannot be
> loaded\n",
> + __func__);
> +
> + loadaddr = load_elf_image_phdr(loadaddr);
I suggest to load DM image using proc_auth_boot API, so that firewall
inforamtion in DM image can be applied, or replay certificate of DM
image and load elf.
> + ret = ti_sci->ops.lpm_ops.lpm_save_addr(ti_sci,
> + mem_addr_lpm->context_save_addr,
> + mem_addr_lpm->size);
> + if (ret)
> + panic("TIFS lpm save addr fail : %x\n", ret);
> +
> + /*
> + * TIFS minimal context restore
> + * This restores also the firewall
> + */
> + ret = ti_sci->ops.lpm_ops.min_context_restore(ti_sci, 0);
> + if (ret)
> + panic("TIFS restore_context failed (%d)\n", ret);
> +
> + /*
> + * Restore TFA in msmc memory
> + */
> + ret = ti_sci->ops.lpm_ops.decrypt_tfa(ti_sci,
> + CONFIG_K3_ATF_LOAD_ADDR);
> + if (ret)
> + panic("%s: TIFS failed to decrytp TFA : %x\n", __func__, ret);
> +
> + /* restore TFA resume vector address in main core */
> + ret = ti_sci->ops.lpm_ops.core_resume(ti_sci);
> + if (ret)
> + panic("ATF failed to resume (%d)\n", ret);
> +
> + return loadaddr;
> +}
> +
> +static void resume_rproc_f(void)
> +{
> + struct power_domain rproc_pwrdmn;
> + struct udevice *dev;
> + int ret;
> +
> + ret = uclass_get_device_by_seq(UCLASS_REMOTEPROC, 1, &dev);
> + if (ret)
> + panic("Unknown remote processor 1 (%d)\n", ret);
> +
> + ret = power_domain_get_by_index(dev, &rproc_pwrdmn, 1);
> + if (ret)
> + panic("power_domain_get_rproc() failed: %d\n", ret);
> +
> + ret = power_domain_on(&rproc_pwrdmn);
> + if (ret)
> + panic("power_domain_on failed: %d\n", ret);
> +}
> +
> +typedef void __noreturn (*image_entry_noargs_t)(void);
> +
> +void __noreturn do_resume(void)
> +{
> + struct lpm_addr_info mem_addr_lpm;
> + image_entry_noargs_t image_entry;
> + size_t sz = FW_IMAGE_SIZE;
> + unsigned long loadaddr;
> + void *image_addr;
> + int ret;
> +
> + ret = extract_lpm_region(&mem_addr_lpm);
> + if (ret)
> + panic("Cannot find valid LPM address range... LPM resume
> failed\n");
> +
> + /*
> + * Disclamer:
Disclaimer
> + * ---------
> + * There's a potential attack vector here, because OPTEE could be
> + * modified before being reloaded.
> + *
> + * Also, if the certificate replay is skipped, then TFA can be
> + * read from MSMC.
> + */
> + ret = rproc_load(1, mem_addr_lpm.atf_cert_addr, 0x200);
> + if (ret)
> + panic("rproc failed to be initialized (%d)\n", ret);
> +
> + image_addr = (void *)mem_addr_lpm.atf_cert_addr;
> + ti_secure_image_auth_apply_fwls(&image_addr, sz);
> +
> + image_addr = (void *)mem_addr_lpm.optee_cert_addr;
> + ti_secure_image_auth_apply_fwls(&image_addr, sz);
> +
> + loadaddr = resume_to_dm_f(&mem_addr_lpm);
> + printf("Starting ATF on ARM64 core...\n\n");
> + resume_rproc_f();
> +
> + image_entry = (image_entry_noargs_t)loadaddr;
> + image_entry();
> +}