Merge tag 'optee-async-notif-fix-for-v6.3' of https://git.linaro.org/people/jens...
[platform/kernel/linux-rpi.git] / drivers / tee / optee / smc_abi.c
index e6e0428..3861ae0 100644 (file)
@@ -7,10 +7,13 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/arm-smccc.h>
+#include <linux/cpuhotplug.h>
 #include <linux/errno.h>
+#include <linux/firmware.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/irqdomain.h>
+#include <linux/kernel.h>
 #include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/of.h>
  */
 #define OPTEE_MIN_STATIC_POOL_ALIGN    9 /* 512 bytes aligned */
 
+/* SMC ABI considers at most a single TEE firmware */
+static unsigned int pcpu_irq_num;
+
+static int optee_cpuhp_enable_pcpu_irq(unsigned int cpu)
+{
+       enable_percpu_irq(pcpu_irq_num, IRQ_TYPE_NONE);
+
+       return 0;
+}
+
+static int optee_cpuhp_disable_pcpu_irq(unsigned int cpu)
+{
+       disable_percpu_irq(pcpu_irq_num);
+
+       return 0;
+}
+
 /*
  * 1. Convert between struct tee_param and struct optee_msg_param
  *
@@ -993,9 +1013,8 @@ static u32 get_async_notif_value(optee_invoke_fn *invoke_fn, bool *value_valid,
        return res.a1;
 }
 
-static irqreturn_t notif_irq_handler(int irq, void *dev_id)
+static irqreturn_t irq_handler(struct optee *optee)
 {
-       struct optee *optee = dev_id;
        bool do_bottom_half = false;
        bool value_valid;
        bool value_pending;
@@ -1018,6 +1037,13 @@ static irqreturn_t notif_irq_handler(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
+static irqreturn_t notif_irq_handler(int irq, void *dev_id)
+{
+       struct optee *optee = dev_id;
+
+       return irq_handler(optee);
+}
+
 static irqreturn_t notif_irq_thread_fn(int irq, void *dev_id)
 {
        struct optee *optee = dev_id;
@@ -1027,7 +1053,7 @@ static irqreturn_t notif_irq_thread_fn(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-static int optee_smc_notif_init_irq(struct optee *optee, u_int irq)
+static int init_irq(struct optee *optee, u_int irq)
 {
        int rc;
 
@@ -1042,12 +1068,103 @@ static int optee_smc_notif_init_irq(struct optee *optee, u_int irq)
        return 0;
 }
 
+static irqreturn_t notif_pcpu_irq_handler(int irq, void *dev_id)
+{
+       struct optee_pcpu *pcpu = dev_id;
+       struct optee *optee = pcpu->optee;
+
+       if (irq_handler(optee) == IRQ_WAKE_THREAD)
+               queue_work(optee->smc.notif_pcpu_wq,
+                          &optee->smc.notif_pcpu_work);
+
+       return IRQ_HANDLED;
+}
+
+static void notif_pcpu_irq_work_fn(struct work_struct *work)
+{
+       struct optee_smc *optee_smc = container_of(work, struct optee_smc,
+                                                  notif_pcpu_work);
+       struct optee *optee = container_of(optee_smc, struct optee, smc);
+
+       optee_smc_do_bottom_half(optee->ctx);
+}
+
+static int init_pcpu_irq(struct optee *optee, u_int irq)
+{
+       struct optee_pcpu __percpu *optee_pcpu;
+       int cpu, rc;
+
+       optee_pcpu = alloc_percpu(struct optee_pcpu);
+       if (!optee_pcpu)
+               return -ENOMEM;
+
+       for_each_present_cpu(cpu)
+               per_cpu_ptr(optee_pcpu, cpu)->optee = optee;
+
+       rc = request_percpu_irq(irq, notif_pcpu_irq_handler,
+                               "optee_pcpu_notification", optee_pcpu);
+       if (rc)
+               goto err_free_pcpu;
+
+       INIT_WORK(&optee->smc.notif_pcpu_work, notif_pcpu_irq_work_fn);
+       optee->smc.notif_pcpu_wq = create_workqueue("optee_pcpu_notification");
+       if (!optee->smc.notif_pcpu_wq) {
+               rc = -EINVAL;
+               goto err_free_pcpu_irq;
+       }
+
+       optee->smc.optee_pcpu = optee_pcpu;
+       optee->smc.notif_irq = irq;
+
+       pcpu_irq_num = irq;
+       rc = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "optee/pcpu-notif:starting",
+                              optee_cpuhp_enable_pcpu_irq,
+                              optee_cpuhp_disable_pcpu_irq);
+       if (!rc)
+               rc = -EINVAL;
+       if (rc < 0)
+               goto err_free_pcpu_irq;
+
+       optee->smc.notif_cpuhp_state = rc;
+
+       return 0;
+
+err_free_pcpu_irq:
+       free_percpu_irq(irq, optee_pcpu);
+err_free_pcpu:
+       free_percpu(optee_pcpu);
+
+       return rc;
+}
+
+static int optee_smc_notif_init_irq(struct optee *optee, u_int irq)
+{
+       if (irq_is_percpu_devid(irq))
+               return init_pcpu_irq(optee, irq);
+       else
+               return init_irq(optee, irq);
+}
+
+static void uninit_pcpu_irq(struct optee *optee)
+{
+       cpuhp_remove_state(optee->smc.notif_cpuhp_state);
+
+       destroy_workqueue(optee->smc.notif_pcpu_wq);
+
+       free_percpu_irq(optee->smc.notif_irq, optee->smc.optee_pcpu);
+       free_percpu(optee->smc.optee_pcpu);
+}
+
 static void optee_smc_notif_uninit_irq(struct optee *optee)
 {
        if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_ASYNC_NOTIF) {
                optee_smc_stop_async_notif(optee->ctx);
                if (optee->smc.notif_irq) {
-                       free_irq(optee->smc.notif_irq, optee);
+                       if (irq_is_percpu_devid(optee->smc.notif_irq))
+                               uninit_pcpu_irq(optee);
+                       else
+                               free_irq(optee->smc.notif_irq, optee);
+
                        irq_dispose_mapping(optee->smc.notif_irq);
                }
        }
@@ -1151,6 +1268,22 @@ static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn)
        return false;
 }
 
+#ifdef CONFIG_OPTEE_INSECURE_LOAD_IMAGE
+static bool optee_msg_api_uid_is_optee_image_load(optee_invoke_fn *invoke_fn)
+{
+       struct arm_smccc_res res;
+
+       invoke_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res);
+
+       if (res.a0 == OPTEE_MSG_IMAGE_LOAD_UID_0 &&
+           res.a1 == OPTEE_MSG_IMAGE_LOAD_UID_1 &&
+           res.a2 == OPTEE_MSG_IMAGE_LOAD_UID_2 &&
+           res.a3 == OPTEE_MSG_IMAGE_LOAD_UID_3)
+               return true;
+       return false;
+}
+#endif
+
 static void optee_msg_get_os_revision(optee_invoke_fn *invoke_fn)
 {
        union {
@@ -1356,6 +1489,120 @@ static void optee_shutdown(struct platform_device *pdev)
                optee_disable_shm_cache(optee);
 }
 
+#ifdef CONFIG_OPTEE_INSECURE_LOAD_IMAGE
+
+#define OPTEE_FW_IMAGE "optee/tee.bin"
+
+static optee_invoke_fn *cpuhp_invoke_fn;
+
+static int optee_cpuhp_probe(unsigned int cpu)
+{
+       /*
+        * Invoking a call on a CPU will cause OP-TEE to perform the required
+        * setup for that CPU. Just invoke the call to get the UID since that
+        * has no side effects.
+        */
+       if (optee_msg_api_uid_is_optee_api(cpuhp_invoke_fn))
+               return 0;
+       else
+               return -EINVAL;
+}
+
+static int optee_load_fw(struct platform_device *pdev,
+                        optee_invoke_fn *invoke_fn)
+{
+       const struct firmware *fw = NULL;
+       struct arm_smccc_res res;
+       phys_addr_t data_pa;
+       u8 *data_buf = NULL;
+       u64 data_size;
+       u32 data_pa_high, data_pa_low;
+       u32 data_size_high, data_size_low;
+       int rc;
+       int hp_state;
+
+       if (!optee_msg_api_uid_is_optee_image_load(invoke_fn))
+               return 0;
+
+       rc = request_firmware(&fw, OPTEE_FW_IMAGE, &pdev->dev);
+       if (rc) {
+               /*
+                * The firmware in the rootfs will not be accessible until we
+                * are in the SYSTEM_RUNNING state, so return EPROBE_DEFER until
+                * that point.
+                */
+               if (system_state < SYSTEM_RUNNING)
+                       return -EPROBE_DEFER;
+               goto fw_err;
+       }
+
+       data_size = fw->size;
+       /*
+        * This uses the GFP_DMA flag to ensure we are allocated memory in the
+        * 32-bit space since TF-A cannot map memory beyond the 32-bit boundary.
+        */
+       data_buf = kmalloc(fw->size, GFP_KERNEL | GFP_DMA);
+       if (!data_buf) {
+               rc = -ENOMEM;
+               goto fw_err;
+       }
+       memcpy(data_buf, fw->data, fw->size);
+       data_pa = virt_to_phys(data_buf);
+       reg_pair_from_64(&data_pa_high, &data_pa_low, data_pa);
+       reg_pair_from_64(&data_size_high, &data_size_low, data_size);
+       goto fw_load;
+
+fw_err:
+       pr_warn("image loading failed\n");
+       data_pa_high = 0;
+       data_pa_low = 0;
+       data_size_high = 0;
+       data_size_low = 0;
+
+fw_load:
+       /*
+        * Always invoke the SMC, even if loading the image fails, to indicate
+        * to EL3 that we have passed the point where it should allow invoking
+        * this SMC.
+        */
+       pr_warn("OP-TEE image loaded from kernel, this can be insecure");
+       invoke_fn(OPTEE_SMC_CALL_LOAD_IMAGE, data_size_high, data_size_low,
+                 data_pa_high, data_pa_low, 0, 0, 0, &res);
+       if (!rc)
+               rc = res.a0;
+       if (fw)
+               release_firmware(fw);
+       kfree(data_buf);
+
+       if (!rc) {
+               /*
+                * We need to initialize OP-TEE on all other running cores as
+                * well. Any cores that aren't running yet will get initialized
+                * when they are brought up by the power management functions in
+                * TF-A which are registered by the OP-TEE SPD. Due to that we
+                * can un-register the callback right after registering it.
+                */
+               cpuhp_invoke_fn = invoke_fn;
+               hp_state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "optee:probe",
+                                            optee_cpuhp_probe, NULL);
+               if (hp_state < 0) {
+                       pr_warn("Failed with CPU hotplug setup for OP-TEE");
+                       return -EINVAL;
+               }
+               cpuhp_remove_state(hp_state);
+               cpuhp_invoke_fn = NULL;
+       }
+
+       return rc;
+}
+#else
+static inline int optee_load_fw(struct platform_device *pdev,
+                               optee_invoke_fn *invoke_fn)
+{
+       return 0;
+}
+#endif
+
 static int optee_probe(struct platform_device *pdev)
 {
        optee_invoke_fn *invoke_fn;
@@ -1374,6 +1621,10 @@ static int optee_probe(struct platform_device *pdev)
        if (IS_ERR(invoke_fn))
                return PTR_ERR(invoke_fn);
 
+       rc = optee_load_fw(pdev, invoke_fn);
+       if (rc)
+               return rc;
+
        if (!optee_msg_api_uid_is_optee_api(invoke_fn)) {
                pr_warn("api uid mismatch\n");
                return -EINVAL;