From: Sam Day <[email protected]> Qualcomm's Secure Channel Manager (SCM) handles secure world calls. SCM calls introduce extra ceremony on top of the standard SMCCC instruction to support passing more arguments than a typical SMC call can accommodate. There's also a qcom-specific smccc quirk.
This new driver determines the expected calling convention on probe. A simple API is introduced to handle querying and calling SCM services. Signed-off-by: Sam Day <[email protected]> --- MAINTAINERS | 2 + drivers/firmware/Kconfig | 9 ++ drivers/firmware/Makefile | 1 + drivers/firmware/qcom-scm.c | 274 ++++++++++++++++++++++++++++++++++++++++++++ include/firmware/qcom/scm.h | 71 ++++++++++++ 5 files changed, 357 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 571af196465..16b1e8e45dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -688,6 +688,8 @@ S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-snapdragon.git F: configs/qcm6490_defconfig F: configs/qcs9100_defconfig +F: drivers/firmware/qcom-scm.c +F: include/firmware/qcom/ F: drivers/*/*/pm8???-* F: drivers/gpio/msm_gpio.c F: drivers/mmc/msm_sdhci.c diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 220de731950..2d5ae35d07f 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -47,5 +47,14 @@ config ARM_SMCCC_FEATURES the PSCI driver is always probed and binds dirvers registered to the Arm SMCCC services if any and reported as supported by the SMCCC firmware. +config QCOM_SCM + bool "Qualcomm Secure Channel Manager" + depends on ARM_SMCCC + select FIRMWARE + help + Enable support for Qualcomm Secure Channel Manager calls. These + calls are used to ask the platform firmware to perform operations + such as configuring secondary CPU boot addresses. + source "drivers/firmware/arm-ffa/Kconfig" source "drivers/firmware/scmi/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 9e7b118b074..0945a2e828d 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_$(PHASE_)ARM_PSCI_FW) += psci.o obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o obj-$(CONFIG_SANDBOX) += firmware-sandbox.o obj-$(CONFIG_ZYNQMP_FIRMWARE) += firmware-zynqmp.o +obj-$(CONFIG_QCOM_SCM) += qcom-scm.o obj-$(CONFIG_SCMI_FIRMWARE) += scmi/ diff --git a/drivers/firmware/qcom-scm.c b/drivers/firmware/qcom-scm.c new file mode 100644 index 00000000000..7bb13c6b372 --- /dev/null +++ b/drivers/firmware/qcom-scm.c @@ -0,0 +1,274 @@ +// 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 <compiler.h> +#include <cpu_func.h> +#include <dm.h> +#include <errno.h> +#include <firmware/qcom/scm.h> +#include <linux/arm-smccc.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <malloc.h> +#include <memalign.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) + +enum qcom_scm_convention { + SMC_CONVENTION_UNKNOWN, + SMC_CONVENTION_LEGACY, + SMC_CONVENTION_ARM_32, + SMC_CONVENTION_ARM_64, +}; + +#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF)) + +#define QCOM_SCM_SVC_INFO 0x06 +#define QCOM_SCM_INFO_IS_CALL_AVAIL 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; +} + +/** + * struct arm_smccc_args + * @args: The array of values used in registers in smc instruction + */ +struct arm_smccc_args { + unsigned long args[8]; +}; + +struct qcom_scm { + enum qcom_scm_convention convention; +}; + +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) +{ + int retry_count = 0; + + do { + __scm_smc_do_quirk(smc, res); + + 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; +} + +static int __qcom_scm_call(const struct qcom_scm_desc *desc, + enum qcom_scm_convention qcom_convention, + struct qcom_scm_res *res) +{ + int arglen = desc->arginfo & 0xf; + void *args = NULL; + int i, ret; + struct arm_smccc_args smc = {0}; + struct arm_smccc_res smc_res; + u32 qcom_smccc_convention; + u32 fnid = SCM_SMC_FNID(desc->svc, desc->cmd); + + switch (qcom_convention) { + case SMC_CONVENTION_ARM_32: + qcom_smccc_convention = ARM_SMCCC_SMC_32; + break; + case SMC_CONVENTION_ARM_64: + qcom_smccc_convention = ARM_SMCCC_SMC_64; + break; + default: + return -EINVAL; + } + + smc.args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, + 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); + + 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; +} + +static enum qcom_scm_convention qcom_scm_detect_convention(void) +{ + 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 = { + SCM_SMC_FNID(QCOM_SCM_SVC_INFO, + QCOM_SCM_INFO_IS_CALL_AVAIL) | + (ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT) + }, + }; + struct qcom_scm_res scm_ret = {0}; + + if (IS_ENABLED(CONFIG_ARM64) && + !__qcom_scm_call(&desc, SMC_CONVENTION_ARM_64, &scm_ret) && + scm_ret.result[0] == 1) + return SMC_CONVENTION_ARM_64; + + if (!__qcom_scm_call(&desc, SMC_CONVENTION_ARM_32, &scm_ret) && + scm_ret.result[0] == 1) + return SMC_CONVENTION_ARM_32; + + return SMC_CONVENTION_UNKNOWN; +} + +static int qcom_scm_get_device(struct udevice **devp) +{ + return uclass_get_device_by_driver(UCLASS_FIRMWARE, + DM_DRIVER_GET(qcom_scm), devp); +} + +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res) +{ + struct qcom_scm *scm; + struct udevice *dev; + int ret; + + ret = qcom_scm_get_device(&dev); + if (ret) + return ret; + + scm = dev_get_priv(dev); + return __qcom_scm_call(desc, scm->convention, res); +} + +bool qcom_scm_is_call_available(u32 svc_id, u32 cmd_id) +{ + 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 = { + fnid | (ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT) + }, + }; + + if (qcom_scm_call(&desc, &scm_ret)) + return false; + + return scm_ret.result[0]; +} + +static int qcom_scm_probe(struct udevice *dev) +{ + struct qcom_scm *scm = dev_get_priv(dev); + + scm->convention = qcom_scm_detect_convention(); + if (scm->convention == SMC_CONVENTION_UNKNOWN) + return -EOPNOTSUPP; + + return 0; +} + +static const struct udevice_id qcom_scm_of_match[] = { + { .compatible = "qcom,scm" }, + { } +}; + +U_BOOT_DRIVER(qcom_scm) = { + .name = "qcom_scm", + .id = UCLASS_FIRMWARE, + .of_match = qcom_scm_of_match, + .probe = qcom_scm_probe, + .priv_auto = sizeof(struct qcom_scm), +}; diff --git a/include/firmware/qcom/scm.h b/include/firmware/qcom/scm.h new file mode 100644 index 00000000000..9f10c769928 --- /dev/null +++ b/include/firmware/qcom/scm.h @@ -0,0 +1,71 @@ +/* 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_H +#define QCOM_SCM_H + +#include <linux/errno.h> +#include <linux/types.h> + +#define QCOM_SCM_SVC_BOOT 0x01 +#define QCOM_SCM_BOOT_SET_ADDR_MC 0x11 +#define QCOM_SCM_BOOT_MC_FLAG_AARCH64 BIT(0) +#define QCOM_SCM_BOOT_MC_FLAG_COLDBOOT BIT(1) + +#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]; +}; + +#if CONFIG_IS_ENABLED(QCOM_SCM) +bool qcom_scm_is_call_available(u32 svc_id, u32 cmd_id); +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res); +#else +static inline bool qcom_scm_is_call_available(u32 svc_id, u32 cmd_id) +{ + return false; +} + +int qcom_scm_call(const struct qcom_scm_desc *desc, struct qcom_scm_res *res) +{ + return -ENOSYS; +} +#endif + +#endif -- 2.54.0

