[PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim
From: Ekansh Gupta
Date: Sun Nov 01 2026 - 17:37:14 EST
From: Ekansh Gupta <ekangupt@xxxxxxxxxxxxxxxx>
Applications written for the /dev/fastrpc-* interface create their
DSP process with an ioctl that names the ELF image by file descriptor.
Serve it by importing the descriptor into the session's context bank and
creating the process with the shared creation code, which takes the
already-imported buffer in place of a handle. Closing the file releases
the process, as closing a DRM file does.
The old interface offers a secure and a non-secure node per domain.
Mirror its rule: on a secure channel, a client that opened the
non-secure node may only create an unsigned process, and only on a
domain that supports them. This sits in front of the shared check that
a signed process needs CAP_SYS_ADMIN, which applies to both interfaces.
An ELF image passed as a bare pointer rather than a descriptor is not
supported, as there is no buffer to resolve for it.
Signed-off-by: Ekansh Gupta <ekangupt@xxxxxxxxxxxxxxxx>
---
drivers/accel/qda/qda_fastrpc.c | 35 ++++++++++++++++++--
drivers/accel/qda/qda_fastrpc.h | 17 ++++++++++
drivers/accel/qda/qda_fastrpc_shim.c | 64 +++++++++++++++++++++++++++++++++---
drivers/accel/qda/qda_ioctl.c | 22 ++++++++++++-
drivers/accel/qda/qda_ioctl.h | 3 ++
drivers/accel/qda/qda_legacy_ops.c | 39 ++++++++++++++++++++++
drivers/accel/qda/qda_legacy_ops.h | 5 +++
7 files changed, 176 insertions(+), 9 deletions(-)
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 4672405ea4b7..35ce60f3de2e 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -757,6 +757,27 @@ int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
return err;
}
+/*
+ * Use the ELF buffer the caller imported itself as argument 2. It is checked
+ * as a buffer named by handle would be: it must be mapped in this session's
+ * context bank, and the image must fit in it, as the DSP would otherwise read
+ * past the end of the buffer.
+ */
+static int qda_fastrpc_use_elf_obj(struct qda_fastrpc_invoke_ctx *ctx,
+ struct drm_gem_object *obj, u32 filelen)
+{
+ if (to_qda_gem_obj(obj)->iommu_dev != ctx->session->assigned_iommu_dev)
+ return -EACCES;
+
+ if (filelen > to_qda_gem_obj(obj)->size)
+ return -EINVAL;
+
+ drm_gem_object_get(obj);
+ ctx->gem_objs[2] = obj;
+
+ return 0;
+}
+
/*
* INIT_RELEASE packed layout:
* [0] remote_session_id sizeof(int) IN
@@ -796,7 +817,8 @@ static int qda_fastrpc_prepare_args_release_process(struct qda_fastrpc_invoke_ct
*/
static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
{
- struct drm_qda_init_create *init = argp;
+ struct qda_init_create_args *create = argp;
+ struct drm_qda_init_create *init = &create->init;
struct qda_fastrpc_create_process_inbuf *inbuf;
struct qda_dev *qdev = ctx->qdev;
struct drm_gem_object *gem_obj;
@@ -853,12 +875,19 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
assign_kernel_gem_arg(ctx, 1, kernel_gem, gv + off_comm, inbuf->namelen);
/* The image starts at the beginning of the buffer the caller named */
ctx->args[2].length = init->filelen;
- ctx->args[2].handle = init->filehandle;
+ if (create->elf_obj) {
+ err = qda_fastrpc_use_elf_obj(ctx, create->elf_obj, init->filelen);
+ if (err)
+ goto out_put;
+ } else {
+ ctx->args[2].handle = init->filehandle;
+ }
assign_kernel_gem_arg(ctx, 3, kernel_gem, gv + off_pages, sizeof(struct qda_phy_page));
+out_put:
drm_gem_object_put(&kernel_gem->base);
- return 0;
+ return err;
}
/*
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index eaebc4e99676..a5bd7661c26c 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -261,6 +261,23 @@ struct qda_legacy_invoke_arg {
s32 fd;
};
+/**
+ * struct qda_init_create_args - Kernel-side arguments for creating a DSP process
+ *
+ * Wraps the UAPI arguments so that a caller with no handle table, such as the
+ * legacy shim, can pass the ELF image as an already imported buffer.
+ */
+struct qda_init_create_args {
+ /** @init: Creation parameters; @init.file must be zero */
+ struct drm_qda_init_create init;
+ /**
+ * @elf_obj: Buffer holding the ELF image, mapped in the session's
+ * context bank, or NULL if @init.filehandle names it. Borrowed: the
+ * caller keeps its reference for the length of the call.
+ */
+ struct drm_gem_object *elf_obj;
+};
+
struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev);
void qda_fastrpc_context_free(struct kref *ref);
void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx);
diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
index 03fce990ccdb..4a2ba20ee01a 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -24,6 +24,11 @@
/* State of one open /dev/fastrpc-* file */
struct qda_shim_file {
struct qda_session *session;
+ /* Opened through the /dev/fastrpc-<dsp>-secure node */
+ bool secure_node;
+ /* Properties of the qda_shim_dev this file was opened on */
+ bool secure_channel;
+ bool unsigned_support;
};
/* One misc device node */
@@ -31,6 +36,7 @@ struct qda_shim_node {
struct qda_shim_dev *sdev;
struct miscdevice miscdev;
char *name;
+ bool secure;
bool registered;
};
@@ -42,11 +48,13 @@ struct qda_shim_node {
* offload, always get both, so that untrusted applications can still reach
* the non-secure node on a secure channel.
*
- * The two nodes behave identically for now; what separates them is applied
- * to the operations that need it as those are added.
+ * What separates the two nodes is applied to the operations that need it,
+ * using the channel properties recorded here.
*/
struct qda_shim_dev {
struct qda_dev *qdev;
+ bool secure_channel;
+ bool unsigned_support;
struct qda_shim_node nodes[2];
};
@@ -89,6 +97,10 @@ static int qda_shim_open(struct inode *inode, struct file *file)
return err;
}
+ sf->secure_node = snode->secure;
+ sf->secure_channel = snode->sdev->secure_channel;
+ sf->unsigned_support = snode->sdev->unsigned_support;
+
file->private_data = sf;
return 0;
@@ -98,6 +110,7 @@ static int qda_shim_release(struct inode *inode, struct file *file)
{
struct qda_shim_file *sf = file->private_data;
+ qda_legacy_ops_table.release(sf->session);
qda_legacy_ops_table.session_destroy(sf->session);
kfree(sf);
@@ -141,6 +154,43 @@ static long qda_shim_alloc_dma_buf(struct qda_shim_file *sf, void __user *argp)
return 0;
}
+/*
+ * As in drivers/misc/fastrpc.c: on a secure channel, a client that came in
+ * through the non-secure node may only create an unsigned PD, and only on a
+ * domain that supports unsigned PD offload.
+ */
+static bool qda_shim_session_rejected(struct qda_shim_file *sf, bool unsigned_pd)
+{
+ if (!sf->secure_node && sf->secure_channel)
+ return !sf->unsigned_support || !unsigned_pd;
+
+ return false;
+}
+
+static long qda_shim_init_create(struct qda_shim_file *sf, void __user *argp)
+{
+ struct drm_gem_object *elf_obj = NULL;
+ struct fastrpc_init_create args;
+ int err;
+
+ if (copy_from_user(&args, argp, sizeof(args)))
+ return -EFAULT;
+
+ if (qda_shim_session_rejected(sf, args.attrs & FASTRPC_MODE_UNSIGNED_MODULE))
+ return -ECONNREFUSED;
+
+ elf_obj = shim_import_fd(sf->session, args.filefd);
+ if (IS_ERR(elf_obj))
+ return PTR_ERR(elf_obj);
+
+ err = qda_legacy_ops_table.init_create(sf->session, elf_obj, args.filelen, args.attrs,
+ args.siglen);
+ if (elf_obj)
+ drm_gem_object_put(elf_obj);
+
+ return err;
+}
+
static long qda_shim_invoke(struct qda_shim_file *sf, void __user *argp)
{
struct qda_legacy_invoke_arg *largs;
@@ -210,6 +260,8 @@ static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void _
switch (cmd) {
case FASTRPC_IOCTL_ALLOC_DMA_BUFF:
return qda_shim_alloc_dma_buf(sf, argp);
+ case FASTRPC_IOCTL_INIT_CREATE:
+ return qda_shim_init_create(sf, argp);
case FASTRPC_IOCTL_INVOKE:
return qda_shim_invoke(sf, argp);
default:
@@ -256,6 +308,7 @@ static int qda_shim_node_register(struct qda_shim_dev *sdev, int i, bool secure)
return -ENOMEM;
snode->sdev = sdev;
+ snode->secure = secure;
snode->miscdev.minor = MISC_DYNAMIC_MINOR;
snode->miscdev.name = snode->name;
snode->miscdev.fops = &qda_shim_fops;
@@ -293,8 +346,6 @@ static int qda_shim_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct qda_dev *qdev = dev_get_platdata(&auxdev->dev);
- bool secure_channel = !of_property_read_bool(qdev->dev->of_node,
- "qcom,non-secure-domain");
struct qda_shim_dev *sdev;
int err;
@@ -303,16 +354,19 @@ static int qda_shim_probe(struct auxiliary_device *auxdev,
return -ENOMEM;
sdev->qdev = qdev;
+ sdev->secure_channel = !of_property_read_bool(qdev->dev->of_node,
+ "qcom,non-secure-domain");
if (!strncmp(qdev->dsp_name, "cdsp", 4) || !strncmp(qdev->dsp_name, "gdsp", 4)) {
/* Both nodes, so signed and unsigned PDs are both reachable */
+ sdev->unsigned_support = true;
err = qda_shim_node_register(sdev, 0, true);
if (!err)
err = qda_shim_node_register(sdev, 1, false);
} else if (!strncmp(qdev->dsp_name, "adsp", 4) ||
!strncmp(qdev->dsp_name, "mdsp", 4) ||
!strncmp(qdev->dsp_name, "sdsp", 4)) {
- err = qda_shim_node_register(sdev, 0, secure_channel);
+ err = qda_shim_node_register(sdev, 0, sdev->secure_channel);
} else {
err = -ENODEV;
}
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index f51ad0bc9e9c..99b8a2517726 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -137,7 +137,27 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
*/
int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *file_priv)
{
- return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE, data, file_priv->driver_priv);
+ struct qda_init_create_args create = {
+ .init = *(struct drm_qda_init_create *)data,
+ };
+
+ return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE, &create, file_priv->driver_priv);
+}
+
+/**
+ * qda_session_init_create() - Create a DSP process from an imported ELF buffer
+ * @session: Session to create the process for
+ * @create: Creation parameters; @create->init.filehandle and @create->init.file
+ * must be zero
+ *
+ * For sessions with no handle table to name the ELF buffer by, such as those
+ * of the legacy shim.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_session_init_create(struct qda_session *session, struct qda_init_create_args *create)
+{
+ return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE, create, session);
}
/**
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index da67f863c03c..b2c7bd25b138 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -8,10 +8,13 @@
#include "qda_drv.h"
+struct qda_init_create_args;
+
int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_session_init_create(struct qda_session *session, struct qda_init_create_args *create);
#endif /* __QDA_IOCTL_H__ */
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index 74ee4995350e..2b3b3f4fb246 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -10,6 +10,7 @@
#include "qda_drv.h"
#include "qda_fastrpc.h"
#include "qda_gem.h"
+#include "qda_ioctl.h"
#include "qda_legacy_ops.h"
#include "qda_prime.h"
@@ -38,6 +39,42 @@ static void legacy_session_destroy(struct qda_session *session)
drm_dev_put(drm_dev);
}
+/*
+ * The ELF image of a legacy caller is named by file descriptor, so it arrives
+ * already imported. An image passed as a bare pointer has no buffer to
+ * resolve, so it is only accepted when there is none, i.e. an empty image.
+ */
+static int legacy_init_create(struct qda_session *session, struct drm_gem_object *elf_obj,
+ u32 filelen, u32 attrs, u32 siglen)
+{
+ struct qda_init_create_args create = {
+ .init = {
+ .filelen = filelen,
+ .attrs = attrs,
+ .siglen = siglen,
+ },
+ .elf_obj = elf_obj,
+ };
+
+ if (!elf_obj && filelen)
+ return -EOPNOTSUPP;
+
+ return qda_session_init_create(session, &create);
+}
+
+static int legacy_release(struct qda_session *session)
+{
+ /* Only a session that created a process has one to release */
+ if (!READ_ONCE(session->init_mem_gem_obj))
+ return 0;
+
+ /* Unplug check only; see qda_shim_ioctl() for why not drm_dev_enter() */
+ if (drm_dev_is_unplugged(&session->qda_dev->drm_dev))
+ return -ENODEV;
+
+ return qda_release_dsp_process(session->qda_dev, session);
+}
+
/*
* As the GEM_CREATE ioctl, but exporting a DMA-BUF rather than installing a
* handle: a session the shim created has no DRM file to hold handles, and the
@@ -81,6 +118,8 @@ static int legacy_invoke(struct qda_session *session, u32 handle, u32 sc,
const struct qda_legacy_ops qda_legacy_ops_table = {
.session_create = legacy_session_create,
.session_destroy = legacy_session_destroy,
+ .init_create = legacy_init_create,
+ .release = legacy_release,
.alloc_dmabuf = legacy_alloc_dmabuf,
.import_dmabuf = legacy_import_dmabuf,
.invoke = legacy_invoke,
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
index ba1ce3c0a22b..05bd1f806c9a 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -30,6 +30,11 @@ struct qda_legacy_ops {
struct qda_session *(*session_create)(struct qda_dev *qdev);
/** @session_destroy: Close a session */
void (*session_destroy)(struct qda_session *session);
+ /** @init_create: Create a DSP process from an imported ELF image */
+ int (*init_create)(struct qda_session *session, struct drm_gem_object *elf_obj,
+ u32 filelen, u32 attrs, u32 siglen);
+ /** @release: Release the session's DSP process, if it has one */
+ int (*release)(struct qda_session *session);
/** @alloc_dmabuf: Allocate a buffer and export it as a DMA-BUF */
struct dma_buf *(*alloc_dmabuf)(struct qda_session *session, u64 size);
/** @import_dmabuf: Map a DMA-BUF into a session's context bank */
--
2.34.1