
These calls are a little more complex than a standard SMCCC instruction. There's 32bit/64bit calling conventions, support for passing more arguments than can fit in the usual registers (by placing a pointer to the additional args in x7), and the use of a qcom-specific smccc quirk.
Signed-off-by: Sam Day me@samcday.com --- arch/arm/mach-snapdragon/Makefile | 1 + arch/arm/mach-snapdragon/qcom-scm.c | 145 +++++++++++++++++++++++++++++++ arch/arm/mach-snapdragon/qcom-scm.h | 165 ++++++++++++++++++++++++++++++++++++ 3 files changed, 311 insertions(+)
diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index 343e825c6fdd05f36e210b138e741b7b7dd606ac..e3b9510d25da040e72aa61668014f4863add6b5a 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -3,5 +3,6 @@ # (C) Copyright 2015 Mateusz Kulikowski mateusz.kulikowski@gmail.com
obj-y += board.o +obj-y += qcom-scm.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o obj-$(CONFIG_OF_LIVE) += of_fixup.o diff --git a/arch/arm/mach-snapdragon/qcom-scm.c b/arch/arm/mach-snapdragon/qcom-scm.c new file mode 100644 index 0000000000000000000000000000000000000000..b9ccd1bbb7c1a43d864f2d5f3804836a06607b11 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom-scm.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 Linaro Ltd. */ + +/* Much of this code was adapted from Linux kernel */ +/* Copyright (c) 2010-2015,2019 The Linux Foundation. All rights reserved. + */ + +#include "qcom-scm.h" + +#define QCOM_SCM_EBUSY_WAIT_MS 30 +#define QCOM_SCM_EBUSY_MAX_RETRY 20 + +#define SCM_SMC_N_REG_ARGS 4 +#define SCM_SMC_FIRST_EXT_IDX (SCM_SMC_N_REG_ARGS - 1) +#define SCM_SMC_N_EXT_ARGS (MAX_QCOM_SCM_ARGS - SCM_SMC_N_REG_ARGS + 1) +#define SCM_SMC_FIRST_REG_IDX 2 +#define SCM_SMC_LAST_REG_IDX (SCM_SMC_FIRST_REG_IDX + SCM_SMC_N_REG_ARGS - 1) + +/** + * struct arm_smccc_args + * @args: The array of values used in registers in smc instruction + */ +struct arm_smccc_args { + unsigned long args[8]; +}; + +static void __scm_smc_do_quirk(const struct arm_smccc_args *smc, + struct arm_smccc_res *res) +{ + unsigned long a0 = smc->args[0]; + struct arm_smccc_quirk quirk = { .id = ARM_SMCCC_QUIRK_QCOM_A6 }; + + quirk.state.a6 = 0; + + do { + arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2], + smc->args[3], smc->args[4], smc->args[5], + quirk.state.a6, smc->args[7], res, &quirk); + + if (res->a0 == QCOM_SCM_INTERRUPTED) + a0 = res->a0; + + } while (res->a0 == QCOM_SCM_INTERRUPTED); +} + +static int __scm_smc_do(struct arm_smccc_args *smc, struct arm_smccc_res *res, + bool atomic) +{ + int retry_count = 0; + + do { + __scm_smc_do_quirk(smc, res); + + if (atomic) + return 0; + + if (res->a0 == QCOM_SCM_V2_EBUSY) { + if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY) + break; + mdelay(QCOM_SCM_EBUSY_WAIT_MS); + } + } while (res->a0 == QCOM_SCM_V2_EBUSY); + + return 0; +} + +int qcom_scm_call(const struct qcom_scm_desc *desc, + enum qcom_scm_convention qcom_convention, + struct qcom_scm_res *res, bool atomic) +{ + int arglen = desc->arginfo & 0xf; + void *args = NULL; + int i, ret; + struct arm_smccc_args smc = {0}; + struct arm_smccc_res smc_res; + u32 smccc_call_type = atomic ? ARM_SMCCC_FAST_CALL : ARM_SMCCC_STD_CALL; + u32 qcom_smccc_convention = (qcom_convention == SMC_CONVENTION_ARM_32) ? + ARM_SMCCC_SMC_32 : ARM_SMCCC_SMC_64; + u32 fnid = SCM_SMC_FNID(desc->svc, desc->cmd); + + smc.args[0] = ARM_SMCCC_CALL_VAL(smccc_call_type, qcom_smccc_convention, + desc->owner, fnid); + smc.args[1] = desc->arginfo; + for (i = 0; i < SCM_SMC_N_REG_ARGS; i++) + smc.args[i + SCM_SMC_FIRST_REG_IDX] = desc->args[i]; + + if (unlikely(arglen > SCM_SMC_N_REG_ARGS)) { + args = malloc_cache_aligned(SCM_SMC_N_EXT_ARGS * sizeof(u64)); + if (!args) + return -ENOMEM; + + if (qcom_smccc_convention == ARM_SMCCC_SMC_32) { + __le32 *args32 = args; + + for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++) + args32[i] = cpu_to_le32(desc->args[i + + SCM_SMC_FIRST_EXT_IDX]); + } else { + __le64 *args64 = args; + + for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++) + args64[i] = cpu_to_le64(desc->args[i + + SCM_SMC_FIRST_EXT_IDX]); + } + + smc.args[SCM_SMC_LAST_REG_IDX] = (phys_addr_t)args; + flush_cache((unsigned long)args, SCM_SMC_N_EXT_ARGS * sizeof(u64)); + } + + ret = __scm_smc_do(&smc, &smc_res, atomic); + + if (args) + free(args); + + if (ret) + return ret; + + if (res) { + res->result[0] = smc_res.a1; + res->result[1] = smc_res.a2; + res->result[2] = smc_res.a3; + } + + return (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0; +} + +bool qcom_scm_is_call_available(u32 svc_id, u32 cmd_id, + enum qcom_scm_convention convention) +{ + u32 fnid = SCM_SMC_FNID(svc_id, cmd_id); + struct qcom_scm_res scm_ret = {0}; + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_INFO, + .cmd = QCOM_SCM_INFO_IS_CALL_AVAIL, + .owner = ARM_SMCCC_OWNER_SIP, + .arginfo = QCOM_SCM_ARGS(1), + .args = { + ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, convention, + ARM_SMCCC_OWNER_SIP, fnid) + }, + }; + if (qcom_scm_call(&desc, convention, &scm_ret, false)) + return false; + return scm_ret.result[0]; +} diff --git a/arch/arm/mach-snapdragon/qcom-scm.h b/arch/arm/mach-snapdragon/qcom-scm.h new file mode 100644 index 0000000000000000000000000000000000000000..053d8b3c8cd44566f62b129e228963a50c678e19 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom-scm.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* This header was adapted from linux/drivers/firmware/qcom/qcom_scm.h */ +/* Copyright (c) 2010-2015,2019 The Linux Foundation. All rights reserved. + */ +#ifndef __QCOM_SCM_INT_H +#define __QCOM_SCM_INT_H + +#include <asm/io.h> +#include <errno.h> +#include <linux/arm-smccc.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <memalign.h> + +enum qcom_scm_convention { + SMC_CONVENTION_UNKNOWN, + SMC_CONVENTION_LEGACY, + SMC_CONVENTION_ARM_32, + SMC_CONVENTION_ARM_64, +}; + +extern enum qcom_scm_convention qcom_scm_convention; + +#define MAX_QCOM_SCM_ARGS 10 +#define MAX_QCOM_SCM_RETS 3 + +#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\ + (((a) & 0x3) << 4) | \ + (((b) & 0x3) << 6) | \ + (((c) & 0x3) << 8) | \ + (((d) & 0x3) << 10) | \ + (((e) & 0x3) << 12) | \ + (((f) & 0x3) << 14) | \ + (((g) & 0x3) << 16) | \ + (((h) & 0x3) << 18) | \ + (((i) & 0x3) << 20) | \ + (((j) & 0x3) << 22) | \ + ((num) & 0xf)) + +#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +/** + * struct qcom_scm_desc + * @arginfo: Metadata describing the arguments in args[] + * @args: The array of arguments for the secure syscall + */ +struct qcom_scm_desc { + u32 svc; + u32 cmd; + u32 arginfo; + u64 args[MAX_QCOM_SCM_ARGS]; + u32 owner; +}; + +/** + * struct qcom_scm_res + * @result: The values returned by the secure syscall + */ +struct qcom_scm_res { + u64 result[MAX_QCOM_SCM_RETS]; +}; + +#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF)) + +#define QCOM_SCM_SVC_BOOT 0x01 +#define QCOM_SCM_BOOT_SET_ADDR 0x01 +#define QCOM_SCM_BOOT_TERMINATE_PC 0x02 +#define QCOM_SCM_BOOT_SDI_CONFIG 0x09 +#define QCOM_SCM_BOOT_SET_DLOAD_MODE 0x10 +#define QCOM_SCM_BOOT_SET_ADDR_MC 0x11 +#define QCOM_SCM_BOOT_SET_REMOTE_STATE 0x0a +#define QCOM_SCM_FLUSH_FLAG_MASK 0x3 +#define QCOM_SCM_BOOT_MAX_CPUS 4 +#define QCOM_SCM_BOOT_MC_FLAG_AARCH64 BIT(0) +#define QCOM_SCM_BOOT_MC_FLAG_COLDBOOT BIT(1) +#define QCOM_SCM_BOOT_MC_FLAG_WARMBOOT BIT(2) + +#define QCOM_SCM_SVC_PIL 0x02 +#define QCOM_SCM_PIL_PAS_INIT_IMAGE 0x01 +#define QCOM_SCM_PIL_PAS_MEM_SETUP 0x02 +#define QCOM_SCM_PIL_PAS_AUTH_AND_RESET 0x05 +#define QCOM_SCM_PIL_PAS_SHUTDOWN 0x06 +#define QCOM_SCM_PIL_PAS_IS_SUPPORTED 0x07 +#define QCOM_SCM_PIL_PAS_MSS_RESET 0x0a + +#define QCOM_SCM_SVC_IO 0x05 +#define QCOM_SCM_IO_READ 0x01 +#define QCOM_SCM_IO_WRITE 0x02 + +#define QCOM_SCM_SVC_INFO 0x06 +#define QCOM_SCM_INFO_IS_CALL_AVAIL 0x01 + +#define QCOM_SCM_SVC_MP 0x0c +#define QCOM_SCM_MP_RESTORE_SEC_CFG 0x02 +#define QCOM_SCM_MP_IOMMU_SECURE_PTBL_SIZE 0x03 +#define QCOM_SCM_MP_IOMMU_SECURE_PTBL_INIT 0x04 +#define QCOM_SCM_MP_IOMMU_SET_CP_POOL_SIZE 0x05 +#define QCOM_SCM_MP_VIDEO_VAR 0x08 +#define QCOM_SCM_MP_ASSIGN 0x16 +#define QCOM_SCM_MP_SHM_BRIDGE_ENABLE 0x1c +#define QCOM_SCM_MP_SHM_BRIDGE_DELETE 0x1d +#define QCOM_SCM_MP_SHM_BRIDGE_CREATE 0x1e + +#define QCOM_SCM_SVC_OCMEM 0x0f +#define QCOM_SCM_OCMEM_LOCK_CMD 0x01 +#define QCOM_SCM_OCMEM_UNLOCK_CMD 0x02 + +#define QCOM_SCM_SVC_ES 0x10 /* Enterprise Security */ +#define QCOM_SCM_ES_INVALIDATE_ICE_KEY 0x03 +#define QCOM_SCM_ES_CONFIG_SET_ICE_KEY 0x04 + +#define QCOM_SCM_SVC_HDCP 0x11 +#define QCOM_SCM_HDCP_INVOKE 0x01 + +#define QCOM_SCM_SVC_LMH 0x13 +#define QCOM_SCM_LMH_LIMIT_PROFILE_CHANGE 0x01 +#define QCOM_SCM_LMH_LIMIT_DCVSH 0x10 + +#define QCOM_SCM_SVC_SMMU_PROGRAM 0x15 +#define QCOM_SCM_SMMU_PT_FORMAT 0x01 +#define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03 +#define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02 + +#define QCOM_SCM_SVC_WAITQ 0x24 +#define QCOM_SCM_WAITQ_RESUME 0x02 +#define QCOM_SCM_WAITQ_GET_WQ_CTX 0x03 + +#define QCOM_SCM_SVC_GPU 0x28 +#define QCOM_SCM_SVC_GPU_INIT_REGS 0x01 + +/* common error codes */ +#define QCOM_SCM_V2_EBUSY -12 +#define QCOM_SCM_ENOMEM -5 +#define QCOM_SCM_EOPNOTSUPP -4 +#define QCOM_SCM_EINVAL_ADDR -3 +#define QCOM_SCM_EINVAL_ARG -2 +#define QCOM_SCM_ERROR -1 +#define QCOM_SCM_INTERRUPTED 1 + +static inline int qcom_scm_remap_error(int err) +{ + switch (err) { + case QCOM_SCM_ERROR: + return -EIO; + case QCOM_SCM_EINVAL_ADDR: + case QCOM_SCM_EINVAL_ARG: + return -EINVAL; + case QCOM_SCM_EOPNOTSUPP: + return -EOPNOTSUPP; + case QCOM_SCM_ENOMEM: + return -ENOMEM; + case QCOM_SCM_V2_EBUSY: + return -EBUSY; + } + return -EINVAL; +} + +bool qcom_scm_is_call_available(u32 svc_id, u32 cmd_id, + enum qcom_scm_convention convention); + +int qcom_scm_call(const struct qcom_scm_desc *desc, + enum qcom_scm_convention qcom_convention, + struct qcom_scm_res *res, bool atomic); + +#endif