summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/qcom_scm.c
diff options
context:
space:
mode:
authorElliot Berman <eberman@codeaurora.org>2020-01-07 22:04:26 +0100
committerBjorn Andersson <bjorn.andersson@linaro.org>2020-01-08 07:14:43 +0100
commit9a434cee773ae15309ac225f27551b5492618e4a (patch)
tree26d7deff138efa039629a0611ada779626d552e5 /drivers/firmware/qcom_scm.c
parentfirmware: qcom_scm: Remove thin wrappers (diff)
downloadlinux-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.c146
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