]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
firmware: qcom_scm: Dynamically support SMCCC and legacy conventions
authorElliot Berman <eberman@codeaurora.org>
Tue, 7 Jan 2020 21:04:26 +0000 (13:04 -0800)
committerBjorn Andersson <bjorn.andersson@linaro.org>
Wed, 8 Jan 2020 06:14:43 +0000 (22:14 -0800)
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>
drivers/firmware/Kconfig
drivers/firmware/Makefile
drivers/firmware/qcom_scm-legacy.c [moved from drivers/firmware/qcom_scm-32.c with 90% similarity]
drivers/firmware/qcom_scm-smc.c [moved from drivers/firmware/qcom_scm-64.c with 57% similarity]
drivers/firmware/qcom_scm.c
drivers/firmware/qcom_scm.h

index e40a77bfe8210fe4a81f87adfcfa2d4fc417aee7..ea869addc89bb046480bf0b3421ada0063313e7b 100644 (file)
@@ -239,14 +239,6 @@ config QCOM_SCM
        depends on ARM || ARM64
        select RESET_CONTROLLER
 
-config QCOM_SCM_32
-       def_bool y
-       depends on QCOM_SCM && ARM
-
-config QCOM_SCM_64
-       def_bool y
-       depends on QCOM_SCM && ARM64
-
 config QCOM_SCM_DOWNLOAD_MODE_DEFAULT
        bool "Qualcomm download mode enabled by default"
        depends on QCOM_SCM
index 747fb73b264cd06dfe7eba69d2e32562e6e0cc7a..e9fb838af4dff087e11737af9c493022312d1943 100644 (file)
@@ -17,9 +17,7 @@ obj-$(CONFIG_ISCSI_IBFT)      += iscsi_ibft.o
 obj-$(CONFIG_FIRMWARE_MEMMAP)  += memmap.o
 obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
 obj-$(CONFIG_FW_CFG_SYSFS)     += qemu_fw_cfg.o
-obj-$(CONFIG_QCOM_SCM)         += qcom_scm.o
-obj-$(CONFIG_QCOM_SCM_64)      += qcom_scm-64.o
-obj-$(CONFIG_QCOM_SCM_32)      += qcom_scm-32.o
+obj-$(CONFIG_QCOM_SCM)         += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
 obj-$(CONFIG_TI_SCI_PROTOCOL)  += ti_sci.o
 obj-$(CONFIG_TRUSTED_FOUNDATIONS) += trusted_foundations.o
 obj-$(CONFIG_TURRIS_MOX_RWTM)  += turris-mox-rwtm.o
similarity index 90%
rename from drivers/firmware/qcom_scm-32.c
rename to drivers/firmware/qcom_scm-legacy.c
index 08220e7f2167327a0d6f607f5c49f560283b99e2..8532e7c78ef72a3e416415ef2cac93cc157ff9ff 100644 (file)
@@ -26,7 +26,6 @@ struct arm_smccc_args {
        unsigned long args[8];
 };
 
-#define SCM_LEGACY_FNID(s, c)  (((s) << 10) | ((c) & 0x3ff))
 
 /**
  * struct scm_legacy_command - one SCM command buffer
@@ -129,8 +128,8 @@ static void __scm_legacy_do(const struct arm_smccc_args *smc,
  * and response buffers is taken care of by qcom_scm_call; however, callers are
  * responsible for any other cached buffers passed over to the secure world.
  */
-int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
-                        struct qcom_scm_res *res)
+int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
+                   struct qcom_scm_res *res)
 {
        u8 arglen = desc->arginfo & 0xf;
        int ret = 0, context_id;
@@ -218,9 +217,9 @@ out:
  * This shall only be used with commands that are guaranteed to be
  * uninterruptable, atomic and SMP safe.
  */
-int qcom_scm_call_atomic(struct device *unused,
-                        const struct qcom_scm_desc *desc,
-                        struct qcom_scm_res *res)
+int scm_legacy_call_atomic(struct device *unused,
+                          const struct qcom_scm_desc *desc,
+                          struct qcom_scm_res *res)
 {
        int context_id;
        struct arm_smccc_res smc_res;
@@ -241,23 +240,3 @@ int qcom_scm_call_atomic(struct device *unused,
 
        return smc_res.a0;
 }
-
-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,
-               .args[0] = SCM_LEGACY_FNID(svc_id, cmd_id),
-               .arginfo = QCOM_SCM_ARGS(1),
-       };
-       struct qcom_scm_res res;
-
-       ret = qcom_scm_call(dev, &desc, &res);
-
-       return ret ? : res.result[0];
-}
-
-void __qcom_scm_init(void)
-{
-}
similarity index 57%
rename from drivers/firmware/qcom_scm-64.c
rename to drivers/firmware/qcom_scm-smc.c
index 4defc7c6f4cd4077448a89e2cbedb07fbb3c0e71..497c13ba98d67f15651fd3979dfacf98475d7c29 100644 (file)
@@ -14,8 +14,6 @@
 
 #include "qcom_scm.h"
 
-#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
-
 /**
  * struct arm_smccc_args
  * @args:      The array of values used in registers in smc instruction
@@ -24,7 +22,6 @@ struct arm_smccc_args {
        unsigned long args[8];
 };
 
-static u64 qcom_smccc_convention = -1;
 static DEFINE_MUTEX(qcom_scm_lock);
 
 #define QCOM_SCM_EBUSY_WAIT_MS 30
@@ -80,8 +77,8 @@ static void __scm_smc_do(const struct arm_smccc_args *smc,
        }  while (res->a0 == QCOM_SCM_V2_EBUSY);
 }
 
-static int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
-                         struct qcom_scm_res *res, bool atomic)
+int scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
+                struct qcom_scm_res *res, bool atomic)
 {
        int arglen = desc->arginfo & 0xf;
        int i;
@@ -90,6 +87,9 @@ static int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
        size_t alloc_len;
        gfp_t flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
        u32 smccc_call_type = atomic ? ARM_SMCCC_FAST_CALL : ARM_SMCCC_STD_CALL;
+       u32 qcom_smccc_convention =
+                       (qcom_scm_convention == SMC_CONVENTION_ARM_32) ?
+                       ARM_SMCCC_SMC_32 : ARM_SMCCC_SMC_64;
        struct arm_smccc_res smc_res;
        struct arm_smccc_args smc = {0};
 
@@ -149,88 +149,3 @@ static int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
 
        return (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
 }
-
-/**
- * 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.
- */
-int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
-                 struct qcom_scm_res *res)
-{
-       might_sleep();
-       return __scm_smc_call(dev, desc, res, false);
-}
-
-/**
- * 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.
- */
-int qcom_scm_call_atomic(struct device *dev, const struct qcom_scm_desc *desc,
-                        struct qcom_scm_res *res)
-{
-       return __scm_smc_call(dev, desc, res, true);
-}
-
-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);
-       desc.args[0] = SCM_SMC_FNID(svc_id, cmd_id) |
-                       (ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT);
-
-       ret = qcom_scm_call(dev, &desc, &res);
-
-       return ret ? : res.result[0];
-}
-
-void __qcom_scm_init(void)
-{
-       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;
-
-       qcom_smccc_convention = ARM_SMCCC_SMC_64;
-       // Device isn't required as there is only one argument - no device
-       // needed to dma_map_single to secure world
-       ret = qcom_scm_call_atomic(NULL, &desc, &res);
-       if (!ret && res.result[0] == 1)
-               goto out;
-
-       qcom_smccc_convention = ARM_SMCCC_SMC_32;
-       ret = qcom_scm_call_atomic(NULL, &desc, &res);
-       if (!ret && res.result[0] == 1)
-               goto out;
-
-       qcom_smccc_convention = -1;
-       BUG();
-out:
-       pr_info("QCOM SCM SMC Convention: %lld\n", qcom_smccc_convention);
-}
index 895f14830e32865c288aa76c530670104ce001c9..059bb0fbae9e5bd810e5f71347b9175123ae9670 100644 (file)
@@ -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
index 9b7b3577821c80bbf328396e9ec7ba47c5e5c52d..d9ed670da222c8f9b910bb8ed43e7d0cbfadeacf 100644 (file)
@@ -3,6 +3,16 @@
  */
 #ifndef __QCOM_SCM_INT_H
 #define __QCOM_SCM_INT_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
 
@@ -50,11 +60,16 @@ struct qcom_scm_res {
        u64 result[MAX_QCOM_SCM_RETS];
 };
 
-extern int qcom_scm_call(struct device *dev, const struct qcom_scm_desc *desc,
-                        struct qcom_scm_res *res);
-extern int qcom_scm_call_atomic(struct device *dev,
-                               const struct qcom_scm_desc *desc,
-                               struct qcom_scm_res *res);
+#define SCM_SMC_FNID(s, c)     ((((s) & 0xFF) << 8) | ((c) & 0xFF))
+extern int scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
+                       struct qcom_scm_res *res, bool atomic);
+
+#define SCM_LEGACY_FNID(s, c)  (((s) << 10) | ((c) & 0x3ff))
+extern int scm_legacy_call_atomic(struct device *dev,
+                                 const struct qcom_scm_desc *desc,
+                                 struct qcom_scm_res *res);
+extern int scm_legacy_call(struct device *dev, const struct qcom_scm_desc *desc,
+                          struct qcom_scm_res *res);
 
 #define QCOM_SCM_SVC_BOOT              0x01
 #define QCOM_SCM_BOOT_SET_ADDR         0x01
@@ -77,8 +92,6 @@ extern int qcom_scm_call_atomic(struct device *dev,
 
 #define QCOM_SCM_SVC_INFO              0x06
 #define QCOM_SCM_INFO_IS_CALL_AVAIL    0x01
-extern int __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
-               u32 cmd_id);
 
 #define QCOM_SCM_SVC_MP                                0x0c
 #define QCOM_SCM_MP_RESTORE_SEC_CFG            0x02