diff options
author | Elliot Berman <eberman@codeaurora.org> | 2020-01-07 22:04:26 +0100 |
---|---|---|
committer | Bjorn Andersson <bjorn.andersson@linaro.org> | 2020-01-08 07:14:43 +0100 |
commit | 9a434cee773ae15309ac225f27551b5492618e4a (patch) | |
tree | 26d7deff138efa039629a0611ada779626d552e5 /drivers/firmware/qcom_scm.c | |
parent | firmware: qcom_scm: Remove thin wrappers (diff) | |
download | linux-9a434cee773ae15309ac225f27551b5492618e4a.tar.xz linux-9a434cee773ae15309ac225f27551b5492618e4a.zip |
firmware: qcom_scm: Dynamically support SMCCC and legacy conventions
Dynamically support SMCCCC and legacy conventions by detecting which
convention to use at runtime. qcom_scm_call_atomic and qcom_scm_call can
then be moved in qcom_scm.c and use underlying convention backend as
appropriate. Thus, rename qcom_scm-64,-32 to reflect that they are
backends for -smc and -legacy, respectively.
Also add support for making SCM calls earlier than when SCM driver
probes to support use cases such as qcom_scm_set_cold_boot_addr. Support
is added by lazily initializing the convention and guarding the query
with a spin lock. The limitation of these early SCM calls is that they
cannot use DMA, as in the case of >4 arguments for SMC convention and
any non-atomic call for legacy convention.
Tested-by: Brian Masney <masneyb@onstation.org> # arm32
Tested-by: Stephan Gerhold <stephan@gerhold.net>
Signed-off-by: Elliot Berman <eberman@codeaurora.org>
Link: https://lore.kernel.org/r/1578431066-19600-18-git-send-email-eberman@codeaurora.org
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Diffstat (limited to 'drivers/firmware/qcom_scm.c')
-rw-r--r-- | drivers/firmware/qcom_scm.c | 146 |
1 files changed, 145 insertions, 1 deletions
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index 895f14830e32..059bb0fbae9e 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -72,6 +72,13 @@ static struct qcom_scm_wb_entry qcom_scm_wb[] = { { .flag = QCOM_SCM_FLAG_WARMBOOT_CPU3 }, }; +static const char *qcom_scm_convention_names[] = { + [SMC_CONVENTION_UNKNOWN] = "unknown", + [SMC_CONVENTION_ARM_32] = "smc arm 32", + [SMC_CONVENTION_ARM_64] = "smc arm 64", + [SMC_CONVENTION_LEGACY] = "smc legacy", +}; + static struct qcom_scm *__scm; static int qcom_scm_clk_enable(void) @@ -107,6 +114,143 @@ static void qcom_scm_clk_disable(void) clk_disable_unprepare(__scm->bus_clk); } +static int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, + u32 cmd_id); + +enum qcom_scm_convention qcom_scm_convention; +static bool has_queried __read_mostly; +static DEFINE_SPINLOCK(query_lock); + +static void __query_convention(void) +{ + unsigned long flags; + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_INFO, + .cmd = QCOM_SCM_INFO_IS_CALL_AVAIL, + .args[0] = SCM_SMC_FNID(QCOM_SCM_SVC_INFO, + QCOM_SCM_INFO_IS_CALL_AVAIL) | + (ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT), + .arginfo = QCOM_SCM_ARGS(1), + .owner = ARM_SMCCC_OWNER_SIP, + }; + struct qcom_scm_res res; + int ret; + + spin_lock_irqsave(&query_lock, flags); + if (has_queried) + goto out; + + qcom_scm_convention = SMC_CONVENTION_ARM_64; + // Device isn't required as there is only one argument - no device + // needed to dma_map_single to secure world + ret = scm_smc_call(NULL, &desc, &res, true); + if (!ret && res.result[0] == 1) + goto out; + + qcom_scm_convention = SMC_CONVENTION_ARM_32; + ret = scm_smc_call(NULL, &desc, &res, true); + if (!ret && res.result[0] == 1) + goto out; + + qcom_scm_convention = SMC_CONVENTION_LEGACY; +out: + has_queried = true; + spin_unlock_irqrestore(&query_lock, flags); + pr_info("qcom_scm: convention: %s\n", + qcom_scm_convention_names[qcom_scm_convention]); +} + +static inline enum qcom_scm_convention __get_convention(void) +{ + if (unlikely(!has_queried)) + __query_convention(); + return qcom_scm_convention; +} + +/** + * qcom_scm_call() - Invoke a syscall in the secure world + * @dev: device + * @svc_id: service identifier + * @cmd_id: command identifier + * @desc: Descriptor structure containing arguments and return values + * + * Sends a command to the SCM and waits for the command to finish processing. + * This should *only* be called in pre-emptible context. + */ +static int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc, + struct qcom_scm_res *res) +{ + might_sleep(); + switch (__get_convention()) { + case SMC_CONVENTION_ARM_32: + case SMC_CONVENTION_ARM_64: + return scm_smc_call(dev, desc, res, false); + case SMC_CONVENTION_LEGACY: + return scm_legacy_call(dev, desc, res); + default: + pr_err("Unknown current SCM calling convention.\n"); + return -EINVAL; + } +} + +/** + * qcom_scm_call_atomic() - atomic variation of qcom_scm_call() + * @dev: device + * @svc_id: service identifier + * @cmd_id: command identifier + * @desc: Descriptor structure containing arguments and return values + * @res: Structure containing results from SMC/HVC call + * + * Sends a command to the SCM and waits for the command to finish processing. + * This can be called in atomic context. + */ +static int qcom_scm_call_atomic(struct device *dev, + const struct qcom_scm_desc *desc, + struct qcom_scm_res *res) +{ + switch (__get_convention()) { + case SMC_CONVENTION_ARM_32: + case SMC_CONVENTION_ARM_64: + return scm_smc_call(dev, desc, res, true); + case SMC_CONVENTION_LEGACY: + return scm_legacy_call_atomic(dev, desc, res); + default: + pr_err("Unknown current SCM calling convention.\n"); + return -EINVAL; + } +} + +static int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, + u32 cmd_id) +{ + int ret; + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_INFO, + .cmd = QCOM_SCM_INFO_IS_CALL_AVAIL, + .owner = ARM_SMCCC_OWNER_SIP, + }; + struct qcom_scm_res res; + + desc.arginfo = QCOM_SCM_ARGS(1); + switch (__get_convention()) { + case SMC_CONVENTION_ARM_32: + case SMC_CONVENTION_ARM_64: + desc.args[0] = SCM_SMC_FNID(svc_id, cmd_id) | + (ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT); + break; + case SMC_CONVENTION_LEGACY: + desc.args[0] = SCM_LEGACY_FNID(svc_id, cmd_id); + break; + default: + pr_err("Unknown SMC convention being used\n"); + return -EINVAL; + } + + ret = qcom_scm_call(dev, &desc, &res); + + return ret ? : res.result[0]; +} + /** * qcom_scm_set_warm_boot_addr() - Set the warm boot address for cpus * @entry: Entry point function for the cpus @@ -971,7 +1115,7 @@ static int qcom_scm_probe(struct platform_device *pdev) __scm = scm; __scm->dev = &pdev->dev; - __qcom_scm_init(); + __query_convention(); /* * If requested enable "download mode", from this point on warmboot |