On Mon, May 29, 2023 at 01:03:50AM +0200, Maximilian Luz wrote:
Add support for SCM calls to Secure OS and the Secure Execution
Environment (SEE) residing in the TrustZone (TZ) via the QSEECOM
interface. This allows communication with Secure/TZ applications, for
example 'uefisecapp' managing access to UEFI variables.
The added interface attempts to automatically detect known and supported
applications, creating a client (auxiliary) device for each one. The
respective client/auxiliary driver is then responsible for managing and
communicating with the application.
While this patch introduces only a very basic interface without the more
advanced features (such as re-entrant and blocking SCM calls and
listeners/callbacks), this is enough to talk to the aforementioned
'uefisecapp'.
Signed-off-by: Maximilian Luz <luzmaximilian@xxxxxxxxx>
---
Changes in v4:
- Remove instantiation of dedicated QSEECOM device and load the driver
via qcom_scm instead. In particular:
- Add a list of tested devices to ensure that we don't run into any
issues with the currently unimplemented re-entrant calls.
- Use the QSEECOM version to check for general availability of the
interface.
- Attempt to automatically detect available QSEECOM applications
(and instantiate respective clients) based on a fixed list.
- Use auxiliary bus and devices for clients instead of MFD.
- Restructure DMA allocations: Use dma_map_single() directly inside
qcom_scm_qseecom_app_send() instead of requiring clients to allocate
DMA memory themselves.
+#ifdef CONFIG_QCOM_SCM_QSEECOM
+
+/* Lock for QSEECOM SCM call executions */
+DEFINE_MUTEX(qcom_scm_qseecom_call_lock);
Missing static keyword.
+/**
+ * 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: Returns zero on success, nonzero on failure.
Nit: You should drop "Returns" here and capitalise Zero. Similar below.
+ */
+/**
+ * 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: Returns 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 = 0x03;
I know this has been reverse engineered, but is it possible to name also
these cmd codes?
+ 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: Returns zero on success, nonzero on failure. Returns -ENOENT if the
+ * app has not been loaded or could not be found.
+ */
+static 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);
+ if (dma_mapping_error(__scm->dev, name_buf_phys)) {
+ kfree(name_buf);
+ dev_err(__scm->dev, "qseecom: failed to map dma address\n");
+ return -EFAULT;
This should be -ENOMEM (you can also just use the return value from
dma_mapping_error()). Similar below.
+ }
+
+ desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+ desc.svc = QSEECOM_TZ_SVC_APP_MGR;
+ desc.cmd = 0x03;
+ 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;
+}
+
+/**
+ * qcom_scm_qseecom_app_send() - Send to and receive data from a given QSEE app.
+ * @client: The QSEECOM client device corresponding to 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 client 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: Returns zero on success, nonzero error code on failure.
+ */
+int qcom_scm_qseecom_app_send(struct qseecom_client *client, void *req,
+ size_t req_size, void *rsp, size_t rsp_size)
+{
+}
+EXPORT_SYMBOL(qcom_scm_qseecom_app_send);
Should this not be EXPORT_SYMBOL_GPL()?
+
+static void qseecom_client_release(struct device *dev)
+{
+ struct qseecom_client *client = container_of(dev, struct qseecom_client, aux_dev.dev);
+
+ kfree(client);
+}
+
+static void qseecom_client_remove(void *data)
+{
+ struct qseecom_client *client = data;
+
+ auxiliary_device_delete(&client->aux_dev);
+ auxiliary_device_uninit(&client->aux_dev);
+}
+
+static int qseecom_client_register(const struct qseecom_app_desc *desc)
+{
+ struct qseecom_client *client;
+ u32 app_id;
+ int ret;
+
+ /* Try to find the app ID, skip device if not found */
+ ret = qcom_scm_qseecom_app_get_id(desc->app_name, &app_id);
+ if (ret)
+ return ret == -ENOENT ? 0 : ret;
+
+ dev_info(__scm->dev, "qseecom: setting up client for %s\n", desc->app_name);
+
+ /* Allocate and set-up the client device */
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ client->aux_dev.name = desc->dev_name;
+ client->aux_dev.dev.parent = __scm->dev;
+ client->aux_dev.dev.release = qseecom_client_release;
+ client->app_id = app_id;
+
+ ret = auxiliary_device_init(&client->aux_dev);
+ if (ret) {
+ kfree(client);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(&client->aux_dev);
+ if (ret) {
+ auxiliary_device_uninit(&client->aux_dev);
+ return ret;
+ }
+
+ /*
+ * Ensure that the device is properly cleaned up in case of removal or
+ * errors.
+ */
+ ret = devm_add_action_or_reset(__scm->dev, qseecom_client_remove, client);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * We do not yet support re-entrant calls via the qseecom interface. To prevent
+ + any potential issues with this, only allow validated devices for now.
+ */
+static const struct of_device_id qcom_scm_qseecom_allowlist[] = {
+ { .compatible = "lenovo,thinkpad-x13s", },
+ { }
+};
+
+static bool qcom_scm_qseecom_device_allowed(void)
+{
+ struct device_node *np;
+ bool match;
+
+ np = of_find_node_by_path("/");
+ match = of_match_node(qcom_scm_qseecom_allowlist, np);
+ of_node_put(np);
+
+ return match;
+}
+
+static const struct qseecom_app_desc qcom_scm_qseecom_apps[] = {};
+
+static int qcom_scm_qseecom_init(void)
+{
+ u32 version;
+ int ret, i;
+
+ /* Try to query the qseecom version, skip qseecom setup if this fails */
+ ret = qcom_scm_qseecom_get_version(&version);
+ if (ret)
+ return 0;
+
+ dev_info(__scm->dev, "qseecom: found qseecom with version 0x%x\n", version);
+
+ /* Check against tested/verified devices */
+ if (!qcom_scm_qseecom_device_allowed()) {
+ dev_info(__scm->dev, "qseecom: untested device, skipping\n");
Perhaps call the helper machine_allowed() and update the commit message
to match (cf. of_machine_is_compatible())?
+ return 0;
+ }
+
+ /* Set up client devices for each base application */
+ for (i = 0; i < ARRAY_SIZE(qcom_scm_qseecom_apps); i++) {
+ ret = qseecom_client_register(&qcom_scm_qseecom_apps[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#else /* CONFIG_QCOM_SCM_QSEECOM */
+
+static int qcom_scm_qseecom_init(void)
+{
+ return 0;
+}
+
+#endif /* CONFIG_QCOM_SCM_QSEECOM */
+
/**
* qcom_scm_is_available() - Checks if SCM is available
*/
@@ -1496,6 +1903,12 @@ static int qcom_scm_probe(struct platform_device *pdev)
__get_convention();
+ ret = qcom_scm_qseecom_init();
+ if (ret < 0) {
+ __scm = NULL;
So as I mentioned in my reply to 2/4, you can still have clients
registered here when you clear the __scm pointer which they rely on
after an error.
Not sure how best to handle this, but perhaps registering a qseecom
platform device here and have it's driver probe defer until scm is
available would work?
That way you could also separate out the qseecom implementation in a
separate file (driver) rather than having the ifdef above.
+ return dev_err_probe(scm->dev, ret, "Failed to initialize qseecom\n");
+ }
+
/*
* If requested enable "download mode", from this point on warmboot
* will cause the boot stages to enter download mode, unless
Overall this looks really good. Nice job!