firmware: qcom_scm: Add support for Qualcomm Secure Execution Environment SCM interface
[linux-2.6-block.git] / drivers / firmware / qcom_scm.c
index 06fe8aca870d7bf5e34c592b69af1fe4dd1e3888..f9d5c31b8ec7afe63f460a052dfa26c08058f603 100644 (file)
@@ -55,6 +55,53 @@ struct qcom_scm_mem_map_info {
        __le64 mem_size;
 };
 
+/**
+ * struct qcom_scm_qseecom_resp - QSEECOM SCM call response.
+ * @result:    Result or status of the SCM call. See &enum qcom_scm_qseecom_result.
+ * @resp_type: Type of the response. See &enum qcom_scm_qseecom_resp_type.
+ * @data:      Response data. The type of this data is given in @resp_type.
+ */
+struct qcom_scm_qseecom_resp {
+       u64 result;
+       u64 resp_type;
+       u64 data;
+};
+
+enum qcom_scm_qseecom_result {
+       QSEECOM_RESULT_SUCCESS                  = 0,
+       QSEECOM_RESULT_INCOMPLETE               = 1,
+       QSEECOM_RESULT_BLOCKED_ON_LISTENER      = 2,
+       QSEECOM_RESULT_FAILURE                  = 0xFFFFFFFF,
+};
+
+enum qcom_scm_qseecom_resp_type {
+       QSEECOM_SCM_RES_APP_ID                  = 0xEE01,
+       QSEECOM_SCM_RES_QSEOS_LISTENER_ID       = 0xEE02,
+};
+
+enum qcom_scm_qseecom_tz_owner {
+       QSEECOM_TZ_OWNER_SIP                    = 2,
+       QSEECOM_TZ_OWNER_TZ_APPS                = 48,
+       QSEECOM_TZ_OWNER_QSEE_OS                = 50
+};
+
+enum qcom_scm_qseecom_tz_svc {
+       QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER       = 0,
+       QSEECOM_TZ_SVC_APP_MGR                  = 1,
+       QSEECOM_TZ_SVC_INFO                     = 6,
+};
+
+enum qcom_scm_qseecom_tz_cmd_app {
+       QSEECOM_TZ_CMD_APP_SEND                 = 1,
+       QSEECOM_TZ_CMD_APP_LOOKUP               = 3,
+};
+
+enum qcom_scm_qseecom_tz_cmd_info {
+       QSEECOM_TZ_CMD_INFO_VERSION             = 3,
+};
+
+#define QSEECOM_MAX_APP_NAME_SIZE              64
+
 /* Each bit configures cold/warm boot address for one of the 4 CPUs */
 static const u8 qcom_scm_cpu_cold_bits[QCOM_SCM_BOOT_MAX_CPUS] = {
        0, BIT(0), BIT(3), BIT(5)
@@ -1321,6 +1368,340 @@ static int qcom_scm_find_dload_address(struct device *dev, u64 *addr)
        return 0;
 }
 
+#ifdef CONFIG_QCOM_QSEECOM
+
+/* Lock for QSEECOM SCM call executions */
+static DEFINE_MUTEX(qcom_scm_qseecom_call_lock);
+
+static int __qcom_scm_qseecom_call(const struct qcom_scm_desc *desc,
+                                  struct qcom_scm_qseecom_resp *res)
+{
+       struct qcom_scm_res scm_res = {};
+       int status;
+
+       /*
+        * QSEECOM SCM calls should not be executed concurrently. Therefore, we
+        * require the respective call lock to be held.
+        */
+       lockdep_assert_held(&qcom_scm_qseecom_call_lock);
+
+       status = qcom_scm_call(__scm->dev, desc, &scm_res);
+
+       res->result = scm_res.result[0];
+       res->resp_type = scm_res.result[1];
+       res->data = scm_res.result[2];
+
+       if (status)
+               return status;
+
+       return 0;
+}
+
+/**
+ * qcom_scm_qseecom_call() - Perform a QSEECOM SCM call.
+ * @desc: SCM call descriptor.
+ * @res:  SCM call response (output).
+ *
+ * Performs the QSEECOM SCM call described by @desc, returning the response in
+ * @rsp.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+static int qcom_scm_qseecom_call(const struct qcom_scm_desc *desc,
+                                struct qcom_scm_qseecom_resp *res)
+{
+       int status;
+
+       /*
+        * Note: Multiple QSEECOM SCM calls should not be executed same time,
+        * so lock things here. This needs to be extended to callback/listener
+        * handling when support for that is implemented.
+        */
+
+       mutex_lock(&qcom_scm_qseecom_call_lock);
+       status = __qcom_scm_qseecom_call(desc, res);
+       mutex_unlock(&qcom_scm_qseecom_call_lock);
+
+       dev_dbg(__scm->dev, "%s: owner=%x, svc=%x, cmd=%x, result=%lld, type=%llx, data=%llx\n",
+               __func__, desc->owner, desc->svc, desc->cmd, res->result,
+               res->resp_type, res->data);
+
+       if (status) {
+               dev_err(__scm->dev, "qseecom: scm call failed with error %d\n", status);
+               return status;
+       }
+
+       /*
+        * TODO: Handle incomplete and blocked calls:
+        *
+        * Incomplete and blocked calls are not supported yet. Some devices
+        * and/or commands require those, some don't. Let's warn about them
+        * prominently in case someone attempts to try these commands with a
+        * device/command combination that isn't supported yet.
+        */
+       WARN_ON(res->result == QSEECOM_RESULT_INCOMPLETE);
+       WARN_ON(res->result == QSEECOM_RESULT_BLOCKED_ON_LISTENER);
+
+       return 0;
+}
+
+/**
+ * qcom_scm_qseecom_get_version() - Query the QSEECOM version.
+ * @version: Pointer where the QSEECOM version will be stored.
+ *
+ * Performs the QSEECOM SCM querying the QSEECOM version currently running in
+ * the TrustZone.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+static int qcom_scm_qseecom_get_version(u32 *version)
+{
+       struct qcom_scm_desc desc = {};
+       struct qcom_scm_qseecom_resp res = {};
+       u32 feature = 10;
+       int ret;
+
+       desc.owner = QSEECOM_TZ_OWNER_SIP;
+       desc.svc = QSEECOM_TZ_SVC_INFO;
+       desc.cmd = QSEECOM_TZ_CMD_INFO_VERSION;
+       desc.arginfo = QCOM_SCM_ARGS(1, QCOM_SCM_VAL);
+       desc.args[0] = feature;
+
+       ret = qcom_scm_qseecom_call(&desc, &res);
+       if (ret)
+               return ret;
+
+       *version = res.result;
+       return 0;
+}
+
+/**
+ * qcom_scm_qseecom_app_get_id() - Query the app ID for a given QSEE app name.
+ * @app_name: The name of the app.
+ * @app_id:   The returned app ID.
+ *
+ * Query and return the application ID of the SEE app identified by the given
+ * name. This returned ID is the unique identifier of the app required for
+ * subsequent communication.
+ *
+ * Return: Zero on success, nonzero on failure, -ENOENT if the app has not been
+ * loaded or could not be found.
+ */
+int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id)
+{
+       unsigned long name_buf_size = QSEECOM_MAX_APP_NAME_SIZE;
+       unsigned long app_name_len = strlen(app_name);
+       struct qcom_scm_desc desc = {};
+       struct qcom_scm_qseecom_resp res = {};
+       dma_addr_t name_buf_phys;
+       char *name_buf;
+       int status;
+
+       if (app_name_len >= name_buf_size)
+               return -EINVAL;
+
+       name_buf = kzalloc(name_buf_size, GFP_KERNEL);
+       if (!name_buf)
+               return -ENOMEM;
+
+       memcpy(name_buf, app_name, app_name_len);
+
+       name_buf_phys = dma_map_single(__scm->dev, name_buf, name_buf_size, DMA_TO_DEVICE);
+       status = dma_mapping_error(__scm->dev, name_buf_phys);
+       if (status) {
+               kfree(name_buf);
+               dev_err(__scm->dev, "qseecom: failed to map dma address\n");
+               return status;
+       }
+
+       desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+       desc.svc = QSEECOM_TZ_SVC_APP_MGR;
+       desc.cmd = QSEECOM_TZ_CMD_APP_LOOKUP;
+       desc.arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_RW, QCOM_SCM_VAL);
+       desc.args[0] = name_buf_phys;
+       desc.args[1] = app_name_len;
+
+       status = qcom_scm_qseecom_call(&desc, &res);
+       dma_unmap_single(__scm->dev, name_buf_phys, name_buf_size, DMA_TO_DEVICE);
+       kfree(name_buf);
+
+       if (status)
+               return status;
+
+       if (res.result == QSEECOM_RESULT_FAILURE)
+               return -ENOENT;
+
+       if (res.result != QSEECOM_RESULT_SUCCESS)
+               return -EINVAL;
+
+       if (res.resp_type != QSEECOM_SCM_RES_APP_ID)
+               return -EINVAL;
+
+       *app_id = res.data;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_get_id);
+
+/**
+ * qcom_scm_qseecom_app_send() - Send to and receive data from a given QSEE app.
+ * @app_id:   The ID of the target app.
+ * @req:      Request buffer sent to the app (must be DMA-mappable).
+ * @req_size: Size of the request buffer.
+ * @rsp:      Response buffer, written to by the app (must be DMA-mappable).
+ * @rsp_size: Size of the response buffer.
+ *
+ * Sends a request to the QSEE app associated with the given ID and read back
+ * its response. The caller must provide two DMA memory regions, one for the
+ * request and one for the response, and fill out the @req region with the
+ * respective (app-specific) request data. The QSEE app reads this and returns
+ * its response in the @rsp region.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size, void *rsp,
+                             size_t rsp_size)
+{
+       struct qcom_scm_qseecom_resp res = {};
+       struct qcom_scm_desc desc = {};
+       dma_addr_t req_phys;
+       dma_addr_t rsp_phys;
+       int status;
+
+       /* Map request buffer */
+       req_phys = dma_map_single(__scm->dev, req, req_size, DMA_TO_DEVICE);
+       status = dma_mapping_error(__scm->dev, req_phys);
+       if (status) {
+               dev_err(__scm->dev, "qseecom: failed to map request buffer\n");
+               return status;
+       }
+
+       /* Map response buffer */
+       rsp_phys = dma_map_single(__scm->dev, rsp, rsp_size, DMA_FROM_DEVICE);
+       status = dma_mapping_error(__scm->dev, rsp_phys);
+       if (status) {
+               dma_unmap_single(__scm->dev, req_phys, req_size, DMA_TO_DEVICE);
+               dev_err(__scm->dev, "qseecom: failed to map response buffer\n");
+               return status;
+       }
+
+       /* Set up SCM call data */
+       desc.owner = QSEECOM_TZ_OWNER_TZ_APPS;
+       desc.svc = QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER;
+       desc.cmd = QSEECOM_TZ_CMD_APP_SEND;
+       desc.arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_VAL,
+                                    QCOM_SCM_RW, QCOM_SCM_VAL,
+                                    QCOM_SCM_RW, QCOM_SCM_VAL);
+       desc.args[0] = app_id;
+       desc.args[1] = req_phys;
+       desc.args[2] = req_size;
+       desc.args[3] = rsp_phys;
+       desc.args[4] = rsp_size;
+
+       /* Perform call */
+       status = qcom_scm_qseecom_call(&desc, &res);
+
+       /* Unmap buffers */
+       dma_unmap_single(__scm->dev, rsp_phys, rsp_size, DMA_FROM_DEVICE);
+       dma_unmap_single(__scm->dev, req_phys, req_size, DMA_TO_DEVICE);
+
+       if (status)
+               return status;
+
+       if (res.result != QSEECOM_RESULT_SUCCESS)
+               return -EIO;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_send);
+
+/*
+ * We do not yet support re-entrant calls via the qseecom interface. To prevent
+ + any potential issues with this, only allow validated machines for now.
+ */
+static const struct of_device_id qcom_scm_qseecom_allowlist[] = {
+       { .compatible = "lenovo,thinkpad-x13s", },
+       { }
+};
+
+static bool qcom_scm_qseecom_machine_is_allowed(void)
+{
+       struct device_node *np;
+       bool match;
+
+       np = of_find_node_by_path("/");
+       if (!np)
+               return false;
+
+       match = of_match_node(qcom_scm_qseecom_allowlist, np);
+       of_node_put(np);
+
+       return match;
+}
+
+static void qcom_scm_qseecom_free(void *data)
+{
+       struct platform_device *qseecom_dev = data;
+
+       platform_device_del(qseecom_dev);
+       platform_device_put(qseecom_dev);
+}
+
+static int qcom_scm_qseecom_init(struct qcom_scm *scm)
+{
+       struct platform_device *qseecom_dev;
+       u32 version;
+       int ret;
+
+       /*
+        * Note: We do two steps of validation here: First, we try to query the
+        * QSEECOM version as a check to see if the interface exists on this
+        * device. Second, we check against known good devices due to current
+        * driver limitations (see comment in qcom_scm_qseecom_allowlist).
+        *
+        * Note that we deliberately do the machine check after the version
+        * check so that we can log potentially supported devices. This should
+        * be safe as downstream sources indicate that the version query is
+        * neither blocking nor reentrant.
+        */
+       ret = qcom_scm_qseecom_get_version(&version);
+       if (ret)
+               return 0;
+
+       dev_info(scm->dev, "qseecom: found qseecom with version 0x%x\n", version);
+
+       if (!qcom_scm_qseecom_machine_is_allowed()) {
+               dev_info(scm->dev, "qseecom: untested machine, skipping\n");
+               return 0;
+       }
+
+       /*
+        * Set up QSEECOM interface device. All application clients will be
+        * set up and managed by the corresponding driver for it.
+        */
+       qseecom_dev = platform_device_alloc("qcom_qseecom", -1);
+       if (!qseecom_dev)
+               return -ENOMEM;
+
+       qseecom_dev->dev.parent = scm->dev;
+
+       ret = platform_device_add(qseecom_dev);
+       if (ret) {
+               platform_device_put(qseecom_dev);
+               return ret;
+       }
+
+       return devm_add_action_or_reset(scm->dev, qcom_scm_qseecom_free, qseecom_dev);
+}
+
+#else /* CONFIG_QCOM_QSEECOM */
+
+static int qcom_scm_qseecom_init(struct qcom_scm *scm)
+{
+       return 0;
+}
+
+#endif /* CONFIG_QCOM_QSEECOM */
+
 /**
  * qcom_scm_is_available() - Checks if SCM is available
  */
@@ -1468,6 +1849,19 @@ static int qcom_scm_probe(struct platform_device *pdev)
        if (download_mode)
                qcom_scm_set_download_mode(true);
 
+       /*
+        * Initialize the QSEECOM interface.
+        *
+        * Note: QSEECOM is fairly self-contained and this only adds the
+        * interface device (the driver of which does most of the heavy
+        * lifting). So any errors returned here should be either -ENOMEM or
+        * -EINVAL (with the latter only in case there's a bug in our code).
+        * This means that there is no need to bring down the whole SCM driver.
+        * Just log the error instead and let SCM live.
+        */
+       ret = qcom_scm_qseecom_init(scm);
+       WARN(ret < 0, "failed to initialize qseecom: %d\n", ret);
+
        return 0;
 }