mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown
       [not found] <20261101160110.3622-1-dezige131@foxmail.com>
@ 2026-11-01 16:01 ` Haozhen Li
  2026-11-02 12:26   ` Konrad Dybcio
  2026-11-01 16:01 ` [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks Haozhen Li
  1 sibling, 1 reply; 6+ messages in thread
From: Haozhen Li @ 2026-11-01 16:01 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio
  Cc: Abel Vesa, Dawid Wróbel, linux-arm-msm, linux-kernel, Haozhen Li

QSEECOM clients in the kernel can currently only talk to trusted
applications that some earlier boot stage happened to load:
qcom_scm_qseecom_app_get_id() looks an app up and
qcom_scm_qseecom_app_send() talks to it, but nothing lets Linux start
one itself.

Add the two missing app-manager commands:

  - qcom_scm_qseecom_app_load() authenticates, loads and starts an app
    from an image staged in TZ memory, handing it over with the
    APP_START call. Both image layouts QTEE accepts are supported: a
    split image (.mdt metadata followed by the segment files, with the
    metadata length passed as mdt_len) and a single-file signed ELF
    (mdt_len == 0), where QTEE parses the metadata from the program
    headers itself.

  - qcom_scm_qseecom_app_shutdown() stops an app and releases its
    secure-world memory and name. Without it an app can only be loaded
    once per boot: QTEE refuses a second load, and only a reboot
    clears the name.

QTEE reads the image address passed to APP_START as a 32-bit value
even under the 64-bit SMC calling convention, and truncates anything
above 4 GiB. Catch that case in qcom_scm_qseecom_app_load() and fail
with -EINVAL rather than pass an address on for silent truncation: on
platforms whose firmware hands out memory above 4 GiB the load would
otherwise fail far from the cause, or worse.

Based on the QSEECOM transport work of Dawid Wrobel and samcday
(https://github.com/samcday/linux).

Signed-off-by: Haozhen Li <dezige131@foxmail.com>
---
 drivers/firmware/qcom/qcom_scm.c       | 123 +++++++++++++++++++++++++
 include/linux/firmware/qcom/qcom_scm.h |  14 +++
 2 files changed, 137 insertions(+)

diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index 43e250fae..c77130693 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright (c) 2010,2015,2019 The Linux Foundation. All rights reserved.
  * Copyright (C) 2015 Linaro Ltd.
+ * Copyright (C) 2026 Dawid Wróbel <me@dawidwrobel.com>
  */
 
 #include <linux/arm-smccc.h>
@@ -115,7 +116,14 @@ enum qcom_scm_qseecom_tz_svc {
 };
 
 enum qcom_scm_qseecom_tz_cmd_app {
+	/*
+	 * APP_START and APP_SEND share the same command number and are
+	 * told apart by owner and service: APP_START is
+	 * QSEE_OS/APP_MGR, APP_SEND is TZ_APPS/APP_ID_PLACEHOLDER.
+	 */
+	QSEECOM_TZ_CMD_APP_START		= 1,
 	QSEECOM_TZ_CMD_APP_SEND			= 1,
+	QSEECOM_TZ_CMD_APP_SHUTDOWN		= 2,
 	QSEECOM_TZ_CMD_APP_LOOKUP		= 3,
 };
 
@@ -2168,6 +2176,121 @@ int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id)
 }
 EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_get_id);
 
+/**
+ * qcom_scm_qseecom_app_load() - Load and start a QSEE application.
+ * @img:     Image buffer, must be allocated from TZ memory.
+ * @mdt_len: Length of the .mdt metadata at the start of @img, or zero if
+ *           @img is a single-file image.
+ * @img_len: Total length of the image in bytes.
+ * @app_id:  ID assigned to the loaded application.
+ *
+ * Hands an application image to QTEE for authentication, loading and
+ * starting, and returns the ID under which qcom_scm_qseecom_app_send()
+ * reaches it.
+ *
+ * Two image layouts are accepted. With a non-zero @mdt_len, @img holds the
+ * .mdt metadata followed by each segment file concatenated in
+ * program-header order; in that layout the metadata segments appear twice,
+ * as the .mdt already contains them, and padding between segments is
+ * dropped. With @mdt_len == 0, @img is a single signed ELF passed as one
+ * file, and QTEE parses the metadata from the program headers itself.
+ *
+ * The image buffer must lie entirely below the 4 GiB physical mark: QTEE
+ * reads the image address as a 32-bit value even under the 64-bit SMC
+ * calling convention, so a buffer above it would be truncated into a wrong
+ * address. Such buffers are rejected here rather than passed on.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+int qcom_scm_qseecom_app_load(void *img, size_t mdt_len, size_t img_len,
+			      u32 *app_id)
+{
+	phys_addr_t img_phys;
+	struct qcom_scm_qseecom_resp res = {};
+	struct qcom_scm_desc desc = {};
+	int status;
+
+	if (!img || !img_len || mdt_len > img_len)
+		return -EINVAL;
+
+	/*
+	 * Unlike the lookup command, which passes a buffer holding the name,
+	 * the image is handed over as a bare physical address: all three
+	 * arguments are plain values.
+	 */
+	desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+	desc.svc = QSEECOM_TZ_SVC_APP_MGR;
+	desc.cmd = QSEECOM_TZ_CMD_APP_START;
+	desc.arginfo = QCOM_SCM_ARGS(3);
+	desc.args[0] = mdt_len;
+	desc.args[1] = img_len;
+
+	img_phys = qcom_tzmem_to_phys(img);
+	if (!img_phys)
+		return -EINVAL;
+
+	/* See the comment on the 4 GiB limit in the function doc above. */
+	if (upper_32_bits(img_phys) || (u64)img_phys + img_len > SZ_4G) {
+		dev_err(__scm->dev, "qseecom: image buffer beyond 4GB: %pap\n", &img_phys);
+		return -EINVAL;
+	}
+
+	desc.args[2] = img_phys;
+
+	status = qcom_scm_qseecom_call(&desc, &res);
+	if (status)
+		return status;
+
+	if (res.result != QSEECOM_RESULT_SUCCESS) {
+		dev_dbg(__scm->dev,
+			"qseecom: app load rejected: result %llu, type %llu, data %llu\n",
+			res.result, res.resp_type, res.data);
+		return -EIO;
+	}
+
+	if (res.resp_type != QSEECOM_SCM_RES_APP_ID)
+		return -EINVAL;
+
+	*app_id = res.data;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_load);
+
+/**
+ * qcom_scm_qseecom_app_shutdown() - Stop and unload a QSEE application.
+ * @app_id: The ID of the application to unload.
+ *
+ * The counterpart to qcom_scm_qseecom_app_load(): stops the application
+ * and releases the secure-world memory it occupies, freeing its name for a
+ * later load. Without this an application can be loaded only once per
+ * boot: QTEE refuses a second load, and only a reboot clears the name.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+int qcom_scm_qseecom_app_shutdown(u32 app_id)
+{
+	struct qcom_scm_qseecom_resp res = {};
+	struct qcom_scm_desc desc = {};
+	int status;
+
+	desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+	desc.svc = QSEECOM_TZ_SVC_APP_MGR;
+	desc.cmd = QSEECOM_TZ_CMD_APP_SHUTDOWN;
+	desc.arginfo = QCOM_SCM_ARGS(1);
+	desc.args[0] = app_id;
+
+	status = qcom_scm_qseecom_call(&desc, &res);
+	if (status)
+		return status;
+
+	if (res.result != QSEECOM_RESULT_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_app_shutdown);
+
 /**
  * qcom_scm_qseecom_app_send() - Send to and receive data from a given QSEE app.
  * @app_id:   The ID of the target app.
diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
index a0a6bc022..8f98b57e1 100644
--- a/include/linux/firmware/qcom/qcom_scm.h
+++ b/include/linux/firmware/qcom/qcom_scm.h
@@ -141,6 +141,9 @@ int qcom_scm_shm_bridge_delete(u64 handle);
 #ifdef CONFIG_QCOM_QSEECOM
 
 int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id);
+int qcom_scm_qseecom_app_load(void *img, size_t mdt_len, size_t img_len,
+			      u32 *app_id);
+int qcom_scm_qseecom_app_shutdown(u32 app_id);
 int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size,
 			      void *rsp, size_t rsp_size);
 
@@ -151,6 +154,17 @@ static inline int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id)
 	return -EINVAL;
 }
 
+static inline int qcom_scm_qseecom_app_load(void *img, size_t mdt_len,
+					    size_t img_len, u32 *app_id)
+{
+	return -EINVAL;
+}
+
+static inline int qcom_scm_qseecom_app_shutdown(u32 app_id)
+{
+	return -EINVAL;
+}
+
 static inline int qcom_scm_qseecom_app_send(u32 app_id,
 					    void *req, size_t req_size,
 					    void *rsp, size_t rsp_size)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks
       [not found] <20261101160110.3622-1-dezige131@foxmail.com>
  2026-11-01 16:01 ` [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown Haozhen Li
@ 2026-11-01 16:01 ` Haozhen Li
  2026-11-02 12:28   ` Konrad Dybcio
  1 sibling, 1 reply; 6+ messages in thread
From: Haozhen Li @ 2026-11-01 16:01 UTC (permalink / raw)
  To: Bjorn Andersson, Konrad Dybcio
  Cc: Abel Vesa, Dawid Wróbel, linux-arm-msm, linux-kernel, Haozhen Li

A QSEE app that needs something from the normal world -- nearly
always file I/O against its secure storage -- does not return from the
SCM call. Instead the call comes back as QSEECOM_RESULT_INCOMPLETE
naming the listener the app waits for, and stays that way until the
normal world answers. Answering is what resumes the app.

The QSEECOM SCM helpers have no way to answer: the TODO in
qcom_scm_qseecom_call() turns an incomplete call into a WARN and
returns success, the app stays parked in TZ, and every later QSEECOM
call returns -EBUSY until the device is power cycled. The machine
allowlist in qcom_scm_qseecom_init() exists because of problems of
this class.

Add listener support:

  - qcom_scm_qseecom_listener_register()/..._unregister() publish a
    listener, with its shared buffer, to TZ.

  - qcom_scm_qseecom_call() now services listener requests while it
    holds the call lock: it dispatches to the registered listener,
    answers with the LISTENER_RESPONSE call, and repeats while the
    interrupted command keeps coming back incomplete. An unknown
    listener id is answered with a failure rather than ignored, since
    ignoring it strands the app; a listener that never settles is
    failed after 256 rounds so a broken app cannot hold the call lock
    forever. A blocked call (the listener is busy in another
    conversation) is reported as -EBUSY as before, without the WARN.

The listener path follows the QSEECOM transport work of Dawid Wrobel
and samcday (https://github.com/samcday/linux), which was verified on
SDM670; on sc8280xp the series is exercised through the fingerprint
trusted application, whose conversation with the normal world stays
on the plain request/response path.

Removing the machine allowlist remains future work: it needs users of
this interface to be audited for re-entrancy first, and this series
does not change which machines QSEECOM comes up on.

Signed-off-by: Haozhen Li <dezige131@foxmail.com>
---
 drivers/firmware/qcom/qcom_scm.c       | 271 +++++++++++++++++++++++--
 include/linux/firmware/qcom/qcom_scm.h |  48 +++++
 2 files changed, 307 insertions(+), 12 deletions(-)

diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index c77130693..7a1a93006 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -112,6 +112,7 @@ enum qcom_scm_qseecom_tz_owner {
 enum qcom_scm_qseecom_tz_svc {
 	QSEECOM_TZ_SVC_APP_ID_PLACEHOLDER	= 0,
 	QSEECOM_TZ_SVC_APP_MGR			= 1,
+	QSEECOM_TZ_SVC_LISTENER			= 2,
 	QSEECOM_TZ_SVC_INFO			= 6,
 };
 
@@ -127,6 +128,12 @@ enum qcom_scm_qseecom_tz_cmd_app {
 	QSEECOM_TZ_CMD_APP_LOOKUP		= 3,
 };
 
+enum qcom_scm_qseecom_tz_cmd_listener {
+	QSEECOM_TZ_CMD_LISTENER_REGISTER	= 1,
+	QSEECOM_TZ_CMD_LISTENER_DEREGISTER	= 2,
+	QSEECOM_TZ_CMD_LISTENER_RESPONSE	= 3,
+};
+
 enum qcom_scm_qseecom_tz_cmd_info {
 	QSEECOM_TZ_CMD_INFO_VERSION		= 3,
 };
@@ -2041,13 +2048,146 @@ static int __qcom_scm_qseecom_call(const struct qcom_scm_desc *desc,
 	return 0;
 }
 
+/* Registered listener services, protected by qcom_scm_qseecom_call_lock. */
+static LIST_HEAD(qcom_scm_qseecom_listeners);
+
+/*
+ * An app that keeps asking for service without ever making progress would
+ * otherwise spin here forever with the call lock held. The real bound is
+ * set by the app; this only stops a broken one from taking the machine
+ * with it.
+ */
+#define QSEECOM_MAX_LISTENER_ROUNDS		256
+
+static struct qcom_scm_qseecom_listener *qcom_scm_qseecom_listener_find(u32 id)
+{
+	struct qcom_scm_qseecom_listener *listener;
+
+	lockdep_assert_held(&qcom_scm_qseecom_call_lock);
+
+	list_for_each_entry(listener, &qcom_scm_qseecom_listeners, node) {
+		if (listener->id == id)
+			return listener;
+	}
+
+	return NULL;
+}
+
+/*
+ * Tell TZ that a listener request has been dealt with. The response is
+ * what resumes the app, so this returns the outcome of the command that
+ * was interrupted -- which may itself be another listener request.
+ */
+static int qcom_scm_qseecom_listener_respond(u32 id, u32 status,
+					     struct qcom_scm_qseecom_resp *res)
+{
+	struct qcom_scm_desc desc = {};
+
+	lockdep_assert_held(&qcom_scm_qseecom_call_lock);
+
+	desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+	desc.svc = QSEECOM_TZ_SVC_LISTENER;
+	desc.cmd = QSEECOM_TZ_CMD_LISTENER_RESPONSE;
+	desc.arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_VAL, QCOM_SCM_VAL);
+	desc.args[0] = id;
+	desc.args[1] = status;
+
+	return __qcom_scm_qseecom_call(&desc, res);
+}
+
+/*
+ * Service listener requests until the interrupted command finally
+ * completes.
+ *
+ * A QSEE app that needs something from the normal world -- reading or
+ * writing its secure storage, overwhelmingly -- does not return; the SCM
+ * call comes back as QSEECOM_RESULT_INCOMPLETE naming the listener it
+ * wants, and stays that way until we answer. Answering is what resumes
+ * it.
+ *
+ * Not answering is not a neutral option. TZ waits, every later QSEECOM
+ * call returns -EBUSY, and the device generally has to be power cycled.
+ * So an unknown listener id is still answered, with a failure, which the
+ * app can at least handle.
+ */
+static int qcom_scm_qseecom_service_listeners(struct qcom_scm_qseecom_resp *res)
+{
+	struct qcom_scm_qseecom_listener *listener;
+	unsigned int rounds = 0;
+	u32 status;
+	u32 id;
+	int ret;
+
+	lockdep_assert_held(&qcom_scm_qseecom_call_lock);
+
+	while (res->result == QSEECOM_RESULT_INCOMPLETE) {
+		if (res->resp_type != QSEECOM_SCM_RES_QSEOS_LISTENER_ID) {
+			dev_err(__scm->dev, "qseecom: invalid response type: 0x%llx\n",
+				res->resp_type);
+			return -EPROTO;
+		}
+
+		if (res->data > U32_MAX) {
+			dev_err(__scm->dev, "qseecom: listener ID out of range: 0x%llx\n",
+				res->data);
+			return -EPROTO;
+		}
+
+		id = res->data;
+
+		if (++rounds > QSEECOM_MAX_LISTENER_ROUNDS) {
+			dev_err(__scm->dev, "qseecom: listener %u loop count exceeded\n", id);
+			status = QSEECOM_LISTENER_FAILURE;
+			ret = qcom_scm_qseecom_listener_respond(id, status, res);
+			if (ret)
+				dev_err(__scm->dev, "qseecom: failed to respond to listener %u: %d\n",
+					id, ret);
+			return -ELOOP;
+		}
+
+		listener = qcom_scm_qseecom_listener_find(id);
+		if (listener) {
+			status = listener->service(listener);
+		} else {
+			dev_warn_once(__scm->dev, "qseecom: unhandled listener ID: %u\n", id);
+			status = QSEECOM_LISTENER_FAILURE;
+		}
+
+		ret = qcom_scm_qseecom_listener_respond(id, status, res);
+		if (ret) {
+			dev_err(__scm->dev, "qseecom: failed to respond to listener %u: %d\n",
+				id, ret);
+			return ret;
+		}
+	}
+
+	/*
+	 * A blocked call is a different problem: the app is waiting on a
+	 * listener that is already busy elsewhere, and unblocking it needs
+	 * TZ_OS_CONTINUE_BLOCKED_REQUEST. Nothing here generates that yet.
+	 *
+	 * Reported the same way as before this function existed: not a
+	 * kernel bug, so no backtrace, and rate-limited because a caller
+	 * that hits it will keep hitting it.
+	 */
+	if (res->result == QSEECOM_RESULT_BLOCKED_ON_LISTENER) {
+		dev_warn_ratelimited(__scm->dev,
+				     "qseecom: blocked on a busy listener\n");
+		return -EBUSY;
+	}
+
+	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.
+ * @rsp. If the call is interrupted by a request for a listener service,
+ * that request is serviced and the call resumed, as many times as the
+ * application asks.
  *
  * Return: Zero on success, nonzero on failure.
  */
@@ -2058,12 +2198,16 @@ static int qcom_scm_qseecom_call(const struct qcom_scm_desc *desc,
 
 	/*
 	 * 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.
+	 * so lock things here. The lock is held across listener servicing
+	 * too: from TZ's point of view the request and our answer are one
+	 * conversation, and letting another call in partway through would
+	 * interleave two of them.
 	 */
 
 	mutex_lock(&qcom_scm_qseecom_call_lock);
 	status = __qcom_scm_qseecom_call(desc, res);
+	if (!status)
+		status = qcom_scm_qseecom_service_listeners(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",
@@ -2075,19 +2219,122 @@ static int qcom_scm_qseecom_call(const struct qcom_scm_desc *desc,
 		return status;
 	}
 
+	return 0;
+}
+
+/**
+ * qcom_scm_qseecom_listener_register() - Offer a listener service to QSEE
+ *                                        apps.
+ * @listener: The listener to register. See &struct qcom_scm_qseecom_listener
+ *            for what the caller has to fill in, and for the rules @service
+ *            has to follow.
+ *
+ * Registers @listener with TZ, after which any app may ask it for service
+ * by its ID. Requests arrive on whichever thread is driving the app at the
+ * time.
+ *
+ * Return: Zero on success, nonzero on failure.
+ */
+int qcom_scm_qseecom_listener_register(struct qcom_scm_qseecom_listener *listener)
+{
+	struct qcom_scm_qseecom_resp res = {};
+	struct qcom_scm_desc desc = {};
+	phys_addr_t sb_phys;
+	int status;
+
+	if (!listener->service || !listener->sb || !listener->sb_len)
+		return -EINVAL;
+
+	sb_phys = qcom_tzmem_to_phys(listener->sb);
+	if (!sb_phys)
+		return -EINVAL;
+
 	/*
-	 * 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.
+	 * The listener service belongs to the same 32-bit era of the
+	 * QSEECOM firmware as APP_START, and nothing establishes that
+	 * it reads the shared-buffer address at full width. An address
+	 * it would truncate would only surface as a secure-world fault
+	 * once a listener request arrives, so -- like
+	 * qcom_scm_qseecom_app_load() for its image -- reject anything
+	 * that does not lie entirely below 4 GiB.
 	 */
-	WARN_ON(res->result == QSEECOM_RESULT_INCOMPLETE);
-	WARN_ON(res->result == QSEECOM_RESULT_BLOCKED_ON_LISTENER);
+	if (upper_32_bits(sb_phys) ||
+	    (u64)sb_phys + listener->sb_len > SZ_4G)
+		return -EINVAL;
 
-	return 0;
+	desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+	desc.svc = QSEECOM_TZ_SVC_LISTENER;
+	desc.cmd = QSEECOM_TZ_CMD_LISTENER_REGISTER;
+	desc.arginfo = QCOM_SCM_ARGS(3, QCOM_SCM_VAL, QCOM_SCM_RW,
+				     QCOM_SCM_VAL);
+	desc.args[0] = listener->id;
+	desc.args[1] = sb_phys;
+	desc.args[2] = listener->sb_len;
+
+	mutex_lock(&qcom_scm_qseecom_call_lock);
+
+	if (qcom_scm_qseecom_listener_find(listener->id)) {
+		mutex_unlock(&qcom_scm_qseecom_call_lock);
+		return -EBUSY;
+	}
+
+	status = __qcom_scm_qseecom_call(&desc, &res);
+	if (!status && res.result != QSEECOM_RESULT_SUCCESS)
+		status = -EIO;
+	if (!status)
+		list_add_tail(&listener->node, &qcom_scm_qseecom_listeners);
+
+	mutex_unlock(&qcom_scm_qseecom_call_lock);
+
+	if (status)
+		dev_err(__scm->dev, "qseecom: failed to register listener %u: %d\n",
+			listener->id, status);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_listener_register);
+
+/**
+ * qcom_scm_qseecom_listener_unregister() - Deregister a QSEECOM listener
+ * @listener: Listener service to deregister
+ *
+ * Return: Zero on success, negative errno on failure.
+ */
+int qcom_scm_qseecom_listener_unregister(struct qcom_scm_qseecom_listener *listener)
+{
+	struct qcom_scm_qseecom_resp res = {};
+	struct qcom_scm_desc desc = {};
+	int status;
+
+	desc.owner = QSEECOM_TZ_OWNER_QSEE_OS;
+	desc.svc = QSEECOM_TZ_SVC_LISTENER;
+	desc.cmd = QSEECOM_TZ_CMD_LISTENER_DEREGISTER;
+	desc.arginfo = QCOM_SCM_ARGS(1, QCOM_SCM_VAL);
+	desc.args[0] = listener->id;
+
+	mutex_lock(&qcom_scm_qseecom_call_lock);
+
+	if (qcom_scm_qseecom_listener_find(listener->id) != listener) {
+		mutex_unlock(&qcom_scm_qseecom_call_lock);
+		return -ENOENT;
+	}
+
+	/* Always remove from list to prevent use-after-free on caller unbind */
+	list_del(&listener->node);
+
+	status = __qcom_scm_qseecom_call(&desc, &res);
+	if (!status && res.result != QSEECOM_RESULT_SUCCESS)
+		status = -EIO;
+
+	mutex_unlock(&qcom_scm_qseecom_call_lock);
+
+	if (status)
+		dev_err(__scm->dev, "qseecom: failed to deregister listener %u: %d\n",
+			listener->id, status);
+
+	return status;
 }
+EXPORT_SYMBOL_GPL(qcom_scm_qseecom_listener_unregister);
 
 /**
  * qcom_scm_qseecom_get_version() - Query the QSEECOM version.
diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
index 8f98b57e1..cf6d9bbc9 100644
--- a/include/linux/firmware/qcom/qcom_scm.h
+++ b/include/linux/firmware/qcom/qcom_scm.h
@@ -6,6 +6,7 @@
 #define __QCOM_SCM_H
 
 #include <linux/err.h>
+#include <linux/list.h>
 #include <linux/types.h>
 #include <linux/cpumask.h>
 
@@ -138,6 +139,39 @@ int qcom_scm_shm_bridge_create(u64 pfn_and_ns_perm_flags,
 			       u64 ns_vmids, u64 *handle);
 int qcom_scm_shm_bridge_delete(u64 handle);
 
+/**
+ * enum qcom_scm_qseecom_listener_status - Return status for listener services
+ * @QSEECOM_LISTENER_SUCCESS: Request processed, shared buffer contains response
+ * @QSEECOM_LISTENER_FAILURE: Failed to process request
+ */
+enum qcom_scm_qseecom_listener_status {
+	QSEECOM_LISTENER_SUCCESS	= 0,
+	QSEECOM_LISTENER_FAILURE	= 1,
+};
+
+/**
+ * struct qcom_scm_qseecom_listener - QSEECOM normal-world listener descriptor
+ * @id:      Unique identifier agreed upon with the target TA
+ * @sb:      Shared memory buffer mapped to TrustZone. Must remain valid
+ *           for as long as the listener is registered, and lie entirely
+ *           below 4 GiB, like the images passed to
+ *           qcom_scm_qseecom_app_load().
+ * @sb_len:  Size of @sb in bytes
+ * @service: Callback invoked to handle normal-world requests
+ * @node:    Internal list entry
+ *
+ * Context: @service is called with qcom_scm_qseecom_call_lock held. It must not
+ * invoke any SCM/QSEECOM functions (which would deadlock) and must not perform
+ * unbounded sleep.
+ */
+struct qcom_scm_qseecom_listener {
+	u32 id;
+	void *sb;
+	size_t sb_len;
+	int (*service)(struct qcom_scm_qseecom_listener *listener);
+	struct list_head node;
+};
+
 #ifdef CONFIG_QCOM_QSEECOM
 
 int qcom_scm_qseecom_app_get_id(const char *app_name, u32 *app_id);
@@ -146,6 +180,8 @@ int qcom_scm_qseecom_app_load(void *img, size_t mdt_len, size_t img_len,
 int qcom_scm_qseecom_app_shutdown(u32 app_id);
 int qcom_scm_qseecom_app_send(u32 app_id, void *req, size_t req_size,
 			      void *rsp, size_t rsp_size);
+int qcom_scm_qseecom_listener_register(struct qcom_scm_qseecom_listener *listener);
+int qcom_scm_qseecom_listener_unregister(struct qcom_scm_qseecom_listener *listener);
 
 #else /* CONFIG_QCOM_QSEECOM */
 
@@ -172,6 +208,18 @@ static inline int qcom_scm_qseecom_app_send(u32 app_id,
 	return -EINVAL;
 }
 
+static inline int
+qcom_scm_qseecom_listener_register(struct qcom_scm_qseecom_listener *listener)
+{
+	return -EINVAL;
+}
+
+static inline int
+qcom_scm_qseecom_listener_unregister(struct qcom_scm_qseecom_listener *listener)
+{
+	return -EINVAL;
+}
+
 #endif /* CONFIG_QCOM_QSEECOM */
 
 int qcom_scm_qtee_invoke_smc(phys_addr_t inbuf, size_t inbuf_size,
-- 
2.43.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown
  2026-11-01 16:01 ` [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown Haozhen Li
@ 2026-11-02 12:26   ` Konrad Dybcio
  2026-11-02 16:27     ` Haozhen Li
  0 siblings, 1 reply; 6+ messages in thread
From: Konrad Dybcio @ 2026-11-02 12:26 UTC (permalink / raw)
  To: Haozhen Li, Bjorn Andersson, Konrad Dybcio
  Cc: Abel Vesa, Dawid Wróbel, linux-arm-msm, linux-kernel,
	Amirreza Zarrabi, Kuldeep Singh, Harshal Dev, Sumit Garg,
	Jorge Ramirez-Ortiz, Dmitry Baryshkov

On 11/1/26 5:01 PM, Haozhen Li wrote:
> QSEECOM clients in the kernel can currently only talk to trusted
> applications that some earlier boot stage happened to load:
> qcom_scm_qseecom_app_get_id() looks an app up and
> qcom_scm_qseecom_app_send() talks to it, but nothing lets Linux start
> one itself.

Adding some relevant folks to Cc for awareness

[...]

> + * The image buffer must lie entirely below the 4 GiB physical mark: QTEE
> + * reads the image address as a 32-bit value even under the 64-bit SMC
> + * calling convention, so a buffer above it would be truncated into a wrong
> + * address. Such buffers are rejected here rather than passed on.

I think that only holds for certain versions

> + *
> + * Return: Zero on success, nonzero on failure.
> + */
> +int qcom_scm_qseecom_app_load(void *img, size_t mdt_len, size_t img_len,
> +			      u32 *app_id)
> +{
> +	phys_addr_t img_phys;
> +	struct qcom_scm_qseecom_resp res = {};
> +	struct qcom_scm_desc desc = {};
> +	int status;

nit: please use reverse-Christmas-tree sorting

[...]

> +	/* See the comment on the 4 GiB limit in the function doc above. */
> +	if (upper_32_bits(img_phys) || (u64)img_phys + img_len > SZ_4G) {

This can overflow on 32b archs, do `|| img_len > SZ_4G - img_phys` instead
(also below)

Konrad

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks
  2026-11-01 16:01 ` [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks Haozhen Li
@ 2026-11-02 12:28   ` Konrad Dybcio
  2026-11-02 16:27     ` Haozhen Li
  0 siblings, 1 reply; 6+ messages in thread
From: Konrad Dybcio @ 2026-11-02 12:28 UTC (permalink / raw)
  To: Haozhen Li, Bjorn Andersson, Konrad Dybcio
  Cc: Abel Vesa, Dawid Wróbel, linux-arm-msm, linux-kernel

On 11/1/26 5:01 PM, Haozhen Li wrote:
> A QSEE app that needs something from the normal world -- nearly
> always file I/O against its secure storage -- does not return from the
> SCM call. Instead the call comes back as QSEECOM_RESULT_INCOMPLETE
> naming the listener the app waits for, and stays that way until the
> normal world answers. Answering is what resumes the app.
> 
> The QSEECOM SCM helpers have no way to answer: the TODO in
> qcom_scm_qseecom_call() turns an incomplete call into a WARN and
> returns success, the app stays parked in TZ, and every later QSEECOM
> call returns -EBUSY until the device is power cycled. The machine
> allowlist in qcom_scm_qseecom_init() exists because of problems of
> this class.

Please extend this RFC with a user, we generally don't merge exported
functions without an in-tree consumer


Konrad

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks
  2026-11-02 12:28   ` Konrad Dybcio
@ 2026-11-02 16:27     ` Haozhen Li
  0 siblings, 0 replies; 6+ messages in thread
From: Haozhen Li @ 2026-11-02 16:27 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: Bjorn Andersson, Konrad Dybcio, Abel Vesa, Dawid Wrobel,
	linux-arm-msm, linux-kernel, Haozhen Li

Hi Konrad,

Thanks for the review.

Following Dawid's response on the cover-letter thread, I am withdrawing
this RFC. Dawid maintains a tee: qseecom driver that consumes exactly
these interfaces (github.com/wrobelda/linux, branch qcom-qseecom-tee),
with a Goodix SPI fingerprint sensor driver on top, and intends to file
a proper RFC for the complete stack himself. That is exactly the
in-tree consumer you asked for, and a much cleaner architectural path
than my standalone SCM helpers.

The fixes and hardened checks discussed here will be offered as a PR
against Dawid's tree to support his upstream submission instead.

Best regards,
Haozhen Li


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown
  2026-11-02 12:26   ` Konrad Dybcio
@ 2026-11-02 16:27     ` Haozhen Li
  0 siblings, 0 replies; 6+ messages in thread
From: Haozhen Li @ 2026-11-02 16:27 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: Bjorn Andersson, Konrad Dybcio, Abel Vesa, Dawid Wrobel,
	linux-arm-msm, linux-kernel, Amirreza Zarrabi, Kuldeep Singh,
	Harshal Dev, Sumit Garg, Jorge Ramirez-Ortiz, Dmitry Baryshkov,
	Haozhen Li

Hi Konrad,

Thanks for pointing these out.

You were completely right about the 4 GiB claim. I probed it on
sc8280xp by OR-ing bit 32 into an otherwise valid image address handed
to APP_START: a firmware reading only the low 32 bits would have
loaded from the untouched address and succeeded, but the call came
back with an explicit error (result 0xffffffffffffffd1, mapped by the
kernel to -EINVAL) instead. There is no silent truncation on this
firmware, and the universal claim in v1 was simply wrong.

Since I am withdrawing this series in favor of Dawid's upcoming RFC
(see the cover-letter thread), all of your feedback -- the
reverse-Christmas-tree sorting, the overflow-safe comparison form you
suggested (img_len > SZ_4G - img_phys), and reframing the 4 GiB check
as defensive hardening -- will go into a PR against his tree instead
of a v2 from me.

Best regards,
Haozhen Li


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-02 16:27 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20261101160110.3622-1-dezige131@foxmail.com>
2026-11-01 16:01 ` [RFC PATCH 1/2] firmware: qcom: scm: Add QSEECOM app loading and shutdown Haozhen Li
2026-11-02 12:26   ` Konrad Dybcio
2026-11-02 16:27     ` Haozhen Li
2026-11-01 16:01 ` [RFC PATCH 2/2] firmware: qcom: scm: Handle QSEECOM listener callbacks Haozhen Li
2026-11-02 12:28   ` Konrad Dybcio
2026-11-02 16:27     ` Haozhen Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®