On Mon, Aug 17, 2026 at 10:17:47AM +0530, Ekansh Gupta wrote:
> Implement the FastRPC remote procedure call path, allowing user-space to
> invoke methods on the DSP via DRM_IOCTL_QDA_REMOTE_INVOKE.
>
> qda_fastrpc.c / qda_fastrpc.h
> Implements the FastRPC protocol layer: argument marshalling
> (qda_fastrpc_invoke_pack), response unmarshalling
> (qda_fastrpc_invoke_unpack), and invocation context lifecycle
> management. Each invocation allocates a qda_fastrpc_invoke_ctx which
> tracks buffer descriptors, GEM objects, and the completion used to
> synchronise with the DSP response.
>
> Buffer arguments are identified by GEM handles. Userspace imports any
> DMA-BUF fd to a GEM handle with DRM_IOCTL_PRIME_FD_TO_HANDLE before
> invoking; the driver never accepts DMA-BUF fds directly. Each argument
> is described by its GEM handle, the user virtual address of the data
> and its length, from which the driver derives the offset within the
> buffer and the page-aligned range to describe to the DSP. Packing
> several arguments into one buffer, and any overlap handling, is left
> to user space.
>
> qda_rpmsg.c
> Implements qda_rpmsg_send_msg() which sends the wire-format
> fastrpc_msg (embedded as the first member of qda_msg) directly via
> rpmsg_send(), and qda_rpmsg_wait_for_rsp() which blocks on the context
> completion. The RPMsg callback dispatches responses to waiting
> contexts via the ctx_xa XArray.
>
> qda_ioctl.c
> qda_ioctl_invoke() drives the full invocation lifecycle: it builds the
> invocation context from the user-supplied arguments, packs the
> arguments into the message buffer, sends the message to the DSP, waits
> for the response, unpacks the output arguments back to user space and
> releases the context.
>
> include/uapi/drm/qda_accel.h
> Adds DRM_IOCTL_QDA_REMOTE_INVOKE with struct drm_qda_invoke_args and
> the per-argument descriptor struct drm_qda_fastrpc_invoke_args.
>
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Ekansh Gupta <[email protected]>
> ---
> Changes in v2:
> - Drop the DMA-BUF fd argument path. Buffer arguments are now identified
> by GEM handles only; user space is responsible for importing fds to
> GEM handles before invoking (Dmitry Baryshkov)
> - Leave argument packing and overlap handling to user space rather than
> supporting several buffer-passing formats in the driver
> (Dmitry Baryshkov)
> ---
> drivers/accel/qda/Makefile | 1 +
> drivers/accel/qda/qda_drv.c | 8 +
> drivers/accel/qda/qda_drv.h | 8 +
> drivers/accel/qda/qda_fastrpc.c | 434
> ++++++++++++++++++++++++++++++++++++++++
> drivers/accel/qda/qda_fastrpc.h | 242 ++++++++++++++++++++++
> drivers/accel/qda/qda_ioctl.c | 83 ++++++++
> drivers/accel/qda/qda_ioctl.h | 1 +
> drivers/accel/qda/qda_rpmsg.c | 91 ++++++++-
> drivers/accel/qda/qda_rpmsg.h | 26 +++
> include/uapi/drm/qda_accel.h | 42 ++++
> 10 files changed, 934 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
> index fb092e56d7f3..2d10420cd1ec 100644
> --- a/drivers/accel/qda/Makefile
> +++ b/drivers/accel/qda/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_ACCEL_QDA) := qda.o
> qda-y := \
> qda_cb.o \
> qda_drv.o \
> + qda_fastrpc.o \
> qda_gem.o \
> qda_ioctl.o \
> qda_memory_dma.o \
> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index a68a07d0ae56..f925bbcfa6e9 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
> @@ -25,6 +25,8 @@ static int qda_open(struct drm_device *dev, struct drm_file
> *file)
>
> qda_file_priv->pid = current->pid;
> qda_file_priv->qda_dev = qda_dev_from_drm(dev);
> + qda_file_priv->remote_session_id =
> +
> atomic_inc_return(&qda_file_priv->qda_dev->remote_session_id_counter);
What happens on the wraparound? What if some of those sessions are still
alive?
> file->driver_priv = qda_file_priv;
>
> return 0;
[...]
> +
> +/*
> + * The driver only accepts GEM handles from user space; userspace imports
> + * DMA-BUF fds with DRM_IOCTL_PRIME_FD_TO_HANDLE before issuing any ioctl
> that
> + * carries a buffer reference.
What else can it accept?
> + *
> + * The caller owns the returned reference and must drop it with
> + * drm_gem_object_put().
> + */
> +static int get_gem_obj_from_handle(struct qda_fastrpc_invoke_ctx *ctx, u32
> handle,
> + struct drm_gem_object **gem_obj)
> +{
> + *gem_obj = drm_gem_object_lookup(ctx->file_priv, handle);
> + if (!*gem_obj)
> + return -ENOENT;
> +
> + return 0;
> +}
> +
> +static void setup_pages_from_gem_obj(struct qda_gem_obj *qda_gem_obj,
> + struct fastrpc_phy_page *pages)
> +{
> + pages->addr = qda_gem_obj->dma_addr;
> + pages->size = qda_gem_obj->size;
> +}
> +
> +static u64 calculate_vma_offset(u64 user_ptr)
> +{
> + struct vm_area_struct *vma;
> + u64 vma_offset = 0;
> +
> + mmap_read_lock(current->mm);
> + vma = find_vma(current->mm, user_ptr);
> + if (vma)
> + vma_offset = (user_ptr & PAGE_MASK) - vma->vm_start;
> + mmap_read_unlock(current->mm);
> +
> + return vma_offset;
> +}
> +
> +static u64 calculate_page_aligned_size(u64 ptr, u64 len)
> +{
> + u64 pg_start = (ptr & PAGE_MASK) >> PAGE_SHIFT;
> + u64 pg_end = ((ptr + len - 1) & PAGE_MASK) >> PAGE_SHIFT;
> +
> + return (pg_end - pg_start + 1) * PAGE_SIZE;
> +}
> +
> +static struct fastrpc_invoke_buf *fastrpc_invoke_buf_start(union
> fastrpc_remote_arg *pra, int len)
> +{
> + return (struct fastrpc_invoke_buf *)(&pra[len]);
Ugh. Is it not start + len * sizeof(union fastrpc_remote_arg)? Can't you
just write it at the caller site?
> +}
> +
> +static struct fastrpc_phy_page *fastrpc_phy_page_start(struct
> fastrpc_invoke_buf *buf, int len)
> +{
> + return (struct fastrpc_phy_page *)(&buf[len]);
> +}
> +
> +static size_t fastrpc_get_meta_size(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> + return (sizeof(struct fastrpc_remote_buf) +
Just one buffer?
> + sizeof(struct fastrpc_invoke_buf) +
> + sizeof(struct fastrpc_phy_page)) * ctx->nscalars +
> + sizeof(u64) * FASTRPC_MAX_HANDLELIST +
> + sizeof(u32) * FASTRPC_MAX_CRCLIST;
> +}
> +
> +/**
> + * qda_fastrpc_cleanup_handlelist() - Release DMA handles the DSP no longer
> needs
> + * @ctx: Invocation context whose response buffer contains the handle list
> + *
> + * The DSP signals handle release by writing GEM handles into the handle list
> + * region of the response buffer. Walk the list and close each handle, which
> + * drops the GEM reference and tears down the IOMMU mapping. This must be
> called
> + * after dma_rmb() has made the DSP's writes visible.
> + */
> +void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> + u64 *handlelist;
> + int i;
> +
> + handlelist = (u64 *)(ctx->pages + ctx->nscalars);
> + for (i = 0; i < FASTRPC_MAX_HANDLELIST; i++) {
> + if (!handlelist[i])
> + break;
> + drm_gem_handle_delete(ctx->file_priv, (u32)handlelist[i]);
> + }
> +}
> +
> +/**
> + * qda_fastrpc_context_free() - Release an invocation context
> + * @ref: Reference counter embedded in the context
> + *
> + * Called when the last reference is dropped, which may be either the caller
> + * that issued the invocation or the response callback. The context is
> removed
> + * from the lookup table here so that a late response can never find a
> context
> + * that is already being destroyed.
> + *
> + * ctx->req, ctx->rsp, ctx->inbuf and ctx->input_pages point into
> kernel-owned
> + * GEM mappings and are released along with ctx->gem_objs[].
> + */
> +void qda_fastrpc_context_free(struct kref *ref)
> +{
> + struct qda_fastrpc_invoke_ctx *ctx;
> + int i;
> +
> + ctx = container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);
> +
> + xa_erase(&ctx->qdev->ctx_xa, ctx->ctxid >> 4);
> +
> + if (ctx->gem_objs) {
> + for (i = 0; i < ctx->nscalars; ++i) {
> + if (ctx->gem_objs[i])
> + drm_gem_object_put(ctx->gem_objs[i]);
> + }
> + kfree(ctx->gem_objs);
> + }
> +
> + if (ctx->msg_gem_obj)
> + drm_gem_object_put(&ctx->msg_gem_obj->base);
> +
> + kfree(ctx->args);
> + kfree(ctx);
> +}
> +
> +/**
> + * qda_fastrpc_flush_pending() - Fail every outstanding invocation
> + * @qdev: Device whose contexts should be flushed
> + *
> + * Called when the remote processor goes away. The DSP will never answer the
> + * in-flight invocations, so this stands in for the response callback: it
> wakes
> + * each waiter with an error and drops the reference the send path took for
> that
> + * callback. The waiter then tears the context down, releasing the GEM
> objects
> + * it pinned.
> + *
> + * Must run after drm_dev_unplug() has drained qda_rpmsg_cb(), so the real
> + * callback can no longer run and this is the only path completing a context.
> + */
> +void qda_fastrpc_flush_pending(struct qda_dev *qdev)
> +{
> + struct qda_fastrpc_invoke_ctx *ctx;
> + unsigned long idx;
> +
> + xa_for_each(&qdev->ctx_xa, idx, ctx) {
> + ctx->retval = -EPIPE;
> + complete(&ctx->work);
> + kref_put(&ctx->refcount, qda_fastrpc_context_free);
> + }
> +}
> +
> +/**
> + * qda_fastrpc_context_alloc() - Allocate a FastRPC invocation context
> + * @qdev: Device whose lookup table the context is registered in
> + *
> + * Return: Pointer to the new context, or ERR_PTR on failure
> + */
> +struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev
> *qdev)
> +{
> + struct qda_fastrpc_invoke_ctx *ctx;
> + int ret;
> + u32 id;
> +
> + ctx = kzalloc_obj(*ctx);
> + if (!ctx)
> + return ERR_PTR(-ENOMEM);
> +
> + INIT_LIST_HEAD(&ctx->node);
> + init_completion(&ctx->work);
> + kref_init(&ctx->refcount);
> + ctx->qdev = qdev;
> + ctx->retval = -1;
> + ctx->pid = current->pid;
> +
> + ret = xa_alloc(&qdev->ctx_xa, &id, ctx, xa_limit_32b, GFP_KERNEL);
> + if (ret) {
> + kfree(ctx);
> + return ERR_PTR(ret);
> + }
> + ctx->ctxid = id << 4;
> +
> + return ctx;
> +}
> +
> +/*
> + * Resolve the physical address of an in/out buffer argument. The buffer is
> + * backed either by a kernel-owned GEM object already recorded in
> + * ctx->gem_objs[i], or by a user-supplied GEM handle in ctx->args[i].handle.
> + */
> +static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
> + union fastrpc_remote_arg *rpra,
> + struct fastrpc_phy_page *pages)
> +{
> + u64 len = ctx->args[i].length;
> + struct qda_gem_obj *qda_gem_obj;
> +
> + rpra[i].buf.pv = ctx->args[i].ptr;
> +
> + if (ctx->gem_objs[i]) {
> + u64 unaligned_addr, offset;
> +
> + qda_gem_obj = to_qda_gem_obj(ctx->gem_objs[i]);
> + offset = ctx->args[i].ptr - (u64)(uintptr_t)qda_gem_obj->virt;
> +
> + unaligned_addr = qda_gem_obj->dma_addr + offset;
> + pages[i].addr = unaligned_addr & PAGE_MASK;
> + pages[i].size = calculate_page_aligned_size(unaligned_addr,
> len);
> + } else if (ctx->args[i].handle) {
> + struct drm_gem_object *gem_obj;
> + int err;
> +
> + err = get_gem_obj_from_handle(ctx, ctx->args[i].handle,
> &gem_obj);
> + if (err)
> + return err;
> +
> + ctx->gem_objs[i] = gem_obj;
> + qda_gem_obj = to_qda_gem_obj(gem_obj);
> +
> + pages[i].addr = qda_gem_obj->dma_addr +
> + calculate_vma_offset(ctx->args[i].ptr);
> + pages[i].size = calculate_page_aligned_size(ctx->args[i].ptr,
> len);
> + } else {
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Resolve a DMA-handle argument. The kernel needs the physical page
> + * descriptor, while the DSP identifies the buffer by its GEM handle.
> + */
> +static int process_dma_handle(struct qda_fastrpc_invoke_ctx *ctx, int i,
> + union fastrpc_remote_arg *rpra,
> + struct fastrpc_phy_page *pages)
> +{
> + struct drm_gem_object *gem_obj;
> + int err;
> +
> + if (!ctx->args[i].handle) {
> + rpra[i].buf.pv = ctx->args[i].ptr;
> + rpra[i].buf.len = ctx->args[i].length;
> + return 0;
> + }
> +
> + err = get_gem_obj_from_handle(ctx, ctx->args[i].handle, &gem_obj);
> + if (err)
> + return err;
> +
> + ctx->gem_objs[i] = gem_obj;
> + setup_pages_from_gem_obj(to_qda_gem_obj(gem_obj), &pages[i]);
> +
> + /* The DSP-facing offset and length fields are 32-bit by wire format */
> + rpra[i].dma.handle = ctx->args[i].handle;
> + rpra[i].dma.len = (u32)ctx->args[i].length;
> + rpra[i].dma.offset = (u32)ctx->args[i].ptr;
> +
> + return 0;
> +}
> +
> +/**
> + * qda_fastrpc_get_header_size() - Compute the FastRPC message header size
> + * @ctx: FastRPC invocation context
> + *
> + * Also caches the intermediate metadata and packet sizes in @ctx for the
> + * subsequent pack step.
> + *
> + * Return: Page-aligned size of the message payload buffer in bytes
> + */
> +size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> + ctx->inbufs = REMOTE_SCALARS_INBUFS(ctx->sc);
> + ctx->metalen = fastrpc_get_meta_size(ctx);
> + ctx->pkt_size = ALIGN(ctx->metalen, FASTRPC_ALIGN);
> + ctx->aligned_pkt_size = PAGE_ALIGN(ctx->pkt_size);
> +
> + return ctx->aligned_pkt_size;
> +}
> +
> +static int qda_fastrpc_get_args(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> + struct fastrpc_invoke_buf *list;
> + struct fastrpc_phy_page *pages;
> + union fastrpc_remote_arg *rpra;
> + int i, err;
> +
> + ctx->msg->buf = ctx->msg_gem_obj->virt;
> + ctx->msg->phys = ctx->msg_gem_obj->dma_addr;
> + memset(ctx->msg->buf, 0, ctx->aligned_pkt_size);
> +
> + rpra = ctx->msg->buf;
> + list = fastrpc_invoke_buf_start(rpra, ctx->nscalars);
> + pages = fastrpc_phy_page_start(list, ctx->nscalars);
> + ctx->rpra = rpra;
> + ctx->list = list;
> + ctx->pages = pages;
> +
> + for (i = 0; i < ctx->nbufs; ++i) {
> + rpra[i].buf.pv = 0;
> + rpra[i].buf.len = ctx->args[i].length;
> + list[i].num = ctx->args[i].length ? 1 : 0;
> + list[i].pgidx = i;
> +
> + if (!ctx->args[i].length)
> + continue;
> +
> + err = process_msg_buffer(ctx, i, rpra, pages);
> + if (err)
> + return err;
> + }
> +
> + for (i = ctx->nbufs; i < ctx->nscalars; ++i) {
> + list[i].num = ctx->args[i].length ? 1 : 0;
> + list[i].pgidx = i;
> +
> + err = process_dma_handle(ctx, i, rpra, pages);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * qda_fastrpc_invoke_pack() - Pack an invocation context into a QDA message
> + * @ctx: FastRPC invocation context
> + * @msg: QDA message to fill in
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct
> qda_msg *msg)
> +{
> + int err;
> +
> + ctx->msg = msg;
> +
> + err = qda_fastrpc_get_args(ctx);
> + if (err)
> + return err;
> +
> + /* Ensure the payload is visible to the DSP before the doorbell */
> + dma_wmb();
> +
> + if (ctx->handle == FASTRPC_INIT_HANDLE)
> + msg->fastrpc.remote_session_id = 0;
> + else
> + msg->fastrpc.remote_session_id = ctx->remote_session_id;
> +
> + msg->fastrpc.tid = ctx->pid;
> + msg->fastrpc.ctx = ctx->ctxid | ctx->pd;
> + msg->fastrpc.handle = ctx->handle;
> + msg->fastrpc.sc = ctx->sc;
> + msg->fastrpc.addr = msg->phys;
> + msg->fastrpc.size = roundup(ctx->pkt_size, PAGE_SIZE);
> + msg->fastrpc_ctx = ctx;
> + msg->file_priv = ctx->file_priv;
> +
> + return 0;
> +}
> +
> +/*
> + * INVOKE_DYNAMIC: the argument descriptors are supplied by user space, which
> + * is also responsible for having imported every buffer to a GEM handle.
> + */
> +static int qda_fastrpc_prepare_args_invoke(struct qda_fastrpc_invoke_ctx
> *ctx, void *argp)
> +{
> + struct drm_qda_invoke_args *invoke_args = argp;
> + struct drm_qda_fastrpc_invoke_args *args;
> + u32 nscalars;
> +
> + ctx->handle = invoke_args->handle;
> + ctx->sc = invoke_args->sc;
> +
> + nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
> + if (!nscalars)
> + return 0;
> +
> + args = kzalloc_objs(*args, nscalars);
> + if (!args)
> + return -ENOMEM;
> +
> + if (copy_from_user(args, u64_to_user_ptr(invoke_args->args),
> + nscalars * sizeof(*args))) {
> + kfree(args);
> + return -EFAULT;
> + }
> +
> + ctx->args = args;
> +
> + return 0;
> +}
> +
> +/**
> + * qda_fastrpc_prepare_args() - Prepare arguments for a FastRPC invocation
> + * @ctx: FastRPC invocation context
> + * @argp: Kernel-side ioctl argument buffer owned by the DRM core
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
> +{
> + int err;
> +
> + switch (ctx->type) {
> + case FASTRPC_RMID_INVOKE_DYNAMIC:
> + err = qda_fastrpc_prepare_args_invoke(ctx, argp);
> + break;
> + default:
> + return -EINVAL;
> + }
> + if (err)
> + return err;
> +
> + ctx->nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
> + ctx->nbufs = REMOTE_SCALARS_INBUFS(ctx->sc) +
> REMOTE_SCALARS_OUTBUFS(ctx->sc);
> +
> + if (ctx->nscalars && !ctx->gem_objs) {
> + ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, ctx->nscalars);
> + if (!ctx->gem_objs)
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
> new file mode 100644
> index 000000000000..a25818923a6e
> --- /dev/null
> +++ b/drivers/accel/qda/qda_fastrpc.h
> @@ -0,0 +1,242 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifndef __QDA_FASTRPC_H__
> +#define __QDA_FASTRPC_H__
> +
> +#include <linux/completion.h>
> +#include <linux/kref.h>
> +#include <linux/list.h>
> +#include <linux/types.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_file.h>
> +#include <drm/qda_accel.h>
> +
> +struct qda_dev;
> +struct qda_gem_obj;
> +struct qda_msg;
> +
> +/*
> + * FastRPC scalar extraction macros
> + *
> + * These macros extract different fields from the scalar value that describes
> + * the arguments passed in a FastRPC invocation.
> + */
> +#define REMOTE_SCALARS_INBUFS(sc) (((sc) >> 16) & 0x0ff)
> +#define REMOTE_SCALARS_OUTBUFS(sc) (((sc) >> 8) & 0x0ff)
> +#define REMOTE_SCALARS_INHANDLES(sc) (((sc) >> 4) & 0x0f)
> +#define REMOTE_SCALARS_OUTHANDLES(sc) ((sc) & 0x0f)
> +#define REMOTE_SCALARS_LENGTH(sc) (REMOTE_SCALARS_INBUFS(sc) + \
> + REMOTE_SCALARS_OUTBUFS(sc) + \
> + REMOTE_SCALARS_INHANDLES(sc) + \
> + REMOTE_SCALARS_OUTHANDLES(sc))
> +
> +/* FastRPC configuration constants */
> +#define FASTRPC_ALIGN 128 /* Alignment
> requirement */
> +#define FASTRPC_MAX_HANDLELIST 16 /* Maximum handles the
> DSP can release */
> +#define FASTRPC_MAX_CRCLIST 64 /* Maximum CRC list entries */
> +
> +/*
> + * FastRPC scalar construction macros
> + *
> + * These macros build the scalar value that describes the arguments
> + * for a FastRPC invocation.
> + */
> +#define FASTRPC_BUILD_SCALARS(attr, method, in, out, oin, oout)
> \
> + (((attr & 0x07) << 29) | \
> + ((method & 0x1f) << 24) | \
> + ((in & 0xff) << 16) | \
> + ((out & 0xff) << 8) | \
> + ((oin & 0x0f) << 4) | \
> + (oout & 0x0f))
> +
> +#define FASTRPC_SCALARS(method, in, out) \
> + FASTRPC_BUILD_SCALARS(0, method, in, out, 0, 0)
> +
> +/**
> + * struct fastrpc_remote_dmahandle - Remote DMA handle descriptor
> + */
> +struct fastrpc_remote_dmahandle {
> + /** @handle: Handle of the buffer */
> + s32 handle;
> + /** @offset: Byte offset within the buffer object */
> + u32 offset;
> + /** @len: Length of the region in bytes */
> + u32 len;
> +};
> +
> +/**
> + * struct fastrpc_remote_buf - Remote buffer descriptor
> + */
> +struct fastrpc_remote_buf {
> + /** @pv: Buffer pointer (user virtual address) */
> + u64 pv;
pv would mean physical something. Also, where do you validate the user's
address? Where do we check that it's ok to access the buffer, that it's
not a garbage?
> + /** @len: Length of the buffer in bytes */
> + u64 len;
> +};
> +
> +/**
> + * union fastrpc_remote_arg - Remote argument (buffer or DMA handle)
> + */
> +union fastrpc_remote_arg {
> + /** @buf: Inline buffer descriptor */
> + struct fastrpc_remote_buf buf;
> + /** @dma: DMA-BUF handle descriptor */
> + struct fastrpc_remote_dmahandle dma;
> +};
> +
> +/**
> + * struct fastrpc_phy_page - Physical page descriptor
> + */
> +struct fastrpc_phy_page {
> + /** @addr: Physical (IOMMU) address of the page */
> + u64 addr;
> + /** @size: Size of the contiguous region in bytes */
> + u64 size;
> +};
> +
> +/**
> + * struct fastrpc_invoke_buf - Invoke buffer descriptor
> + */
> +struct fastrpc_invoke_buf {
> + /** @num: Number of contiguous physical regions */
> + u32 num;
> + /** @pgidx: Index into the physical page array */
> + u32 pgidx;
> +};
> +
> +/**
> + * struct fastrpc_msg - FastRPC wire message for remote invocations
> + *
> + * Sent to the remote processor via RPMsg. This is the exact layout
> + * the DSP expects; do not reorder or add fields without DSP firmware
> + * coordination.
__packed?
> + */
> +struct fastrpc_msg {
> + /** @remote_session_id: Session identifier on the remote processor */
> + int remote_session_id;
Is it int or u32?
> + /** @tid: Thread ID of the invoking thread */
> + int tid;
> + /** @ctx: Context identifier for matching request/response */
> + u64 ctx;
> + /** @handle: Handle of the remote method to invoke */
> + u32 handle;
> + /** @sc: Scalars value encoding in/out buffer counts */
> + u32 sc;
> + /** @addr: Physical address of the message payload buffer */
> + u64 addr;
> + /** @size: Size of the message payload in bytes */
> + u64 size;
> +};
> +
> +/**
> + * struct qda_fastrpc_invoke_ctx - Remote procedure call invocation context
> + *
> + * Maintains all state for a single remote procedure call, including buffer
> + * management, synchronisation, and result handling.
> + */
> +struct qda_fastrpc_invoke_ctx {
> + /** @node: List node for linking contexts in a queue */
> + struct list_head node;
> + /** @qdev: Device owning the XArray this context is registered in */
> + struct qda_dev *qdev;
> + /** @ctxid: Unique context identifier (XArray key shifted left by 4) */
Why is it shifted by 4? Why not by 3?
> + u64 ctxid;
> + /** @inbufs: Number of input buffers */
> + int inbufs;
> + /** @nscalars: Total number of scalar arguments */
> + int nscalars;
> + /** @nbufs: Total number of buffer arguments (inbufs + outbufs) */
> + int nbufs;
> + /** @pid: Process ID of the calling process */
> + int pid;
> + /** @retval: Status code reported by the DSP for this invocation */
> + int retval;
> + /** @remote_session_id: Session identifier on the remote processor */
> + int remote_session_id;
is pid linked to remote_session_id?
> + /** @pd: Protection domain identifier encoded into the context ID */
If it's already encoded, why do you need it here?c
> + int pd;
> + /** @type: Invocation type (e.g. FASTRPC_RMID_INVOKE_DYNAMIC) */
> + u32 type;
What other types can exist?
> + /** @sc: Scalars value encoding in/out buffer counts */
> + u32 sc;
Remove it and calculate from in/outbufs above?
> + /** @handle: Handle of the remote method being invoked */
> + u32 handle;
> + /** @metalen: Length of the FastRPC metadata header in bytes */
> + size_t metalen;
> + /** @pkt_size: Total payload size in bytes */
> + u64 pkt_size;
> + /** @aligned_pkt_size: Page-aligned payload size for GEM allocation */
???
> + u64 aligned_pkt_size;
> + /** @list: Array of invoke buffer descriptors */
> + struct fastrpc_invoke_buf *list;
> + /** @pages: Array of physical page descriptors for all arguments */
> + struct fastrpc_phy_page *pages;
> + /** @input_pages: Array of physical page descriptors for input buffers
> */
> + struct fastrpc_phy_page *input_pages;
> + /** @work: Completion used to synchronise with the DSP response */
> + struct completion work;
> + /** @msg: Pointer to the QDA message structure for this invocation */
> + struct qda_msg *msg;
> + /** @rpra: Array of remote procedure arguments */
> + union fastrpc_remote_arg *rpra;
> + /** @gem_objs: Array of GEM objects imported for argument buffers */
> + struct drm_gem_object **gem_objs;
> + /** @args: Invoke argument descriptors */
> + struct drm_qda_fastrpc_invoke_args *args;
> + /** @refcount: Reference counter for context lifetime management */
What for?
> + struct kref refcount;
> + /** @msg_gem_obj: GEM object backing the message payload buffer */
> + struct qda_gem_obj *msg_gem_obj;
> + /** @file_priv: DRM file private data */
> + struct drm_file *file_priv;
> + /**
> + * @req: Request buffer for the internal init/map/unmap calls. Points
> + * into a kernel-owned GEM mapping tracked by @gem_objs, so it must
> + * never be freed directly.
> + */
> + void *req;
Why do you need separate pointers here? Can't you be getting them from
msm_gem_obj?
> + /** @rsp: Response buffer, same lifetime rules as @req */
> + void *rsp;
> + /** @inbuf: Process-create input buffer, same lifetime rules as @req */
> + void *inbuf;
> +};
> +
> +/**
> + * struct qda_msg - FastRPC message with kernel-internal bookkeeping
> + */
> +struct qda_msg {
> + /**
> + * @fastrpc: Wire-format message sent to the DSP via RPMsg.
> + * Must be the first member.
> + */
> + struct fastrpc_msg fastrpc;
> + /** @buf: Kernel virtual address of the payload buffer */
> + void *buf;
> + /** @phys: Physical/DMA address of the payload buffer */
> + u64 phys;
> + /** @ret: Return value from the remote processor */
> + int ret;
> + /** @fastrpc_ctx: Back-pointer to the owning invocation context */
> + struct qda_fastrpc_invoke_ctx *fastrpc_ctx;
> + /** @file_priv: DRM file private data for GEM object lookup */
> + struct drm_file *file_priv;
> +};
> +
> +/* Remote Method ID table - identifies initialization and control operations
> */
> +#define FASTRPC_RMID_INVOKE_DYNAMIC 0xFFFFFFFFU /* Dynamic method
> invocation */
> +
> +/* Common handle for initialization operations */
> +#define FASTRPC_INIT_HANDLE 0x1
> +
> +void qda_fastrpc_context_free(struct kref *ref);
> +void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx);
> +void qda_fastrpc_flush_pending(struct qda_dev *qdev);
> +struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev
> *qdev);
> +int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp);
> +size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx);
> +int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct
> qda_msg *msg);
> +
> +#endif /* __QDA_FASTRPC_H__ */
> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index f65325c80a12..b7ee4899ba74 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -1,10 +1,13 @@
> // SPDX-License-Identifier: GPL-2.0-only
> // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> #include <drm/drm_ioctl.h>
> +#include <drm/drm_print.h>
> #include <drm/qda_accel.h>
> #include "qda_drv.h"
> +#include "qda_fastrpc.h"
> #include "qda_gem.h"
> #include "qda_ioctl.h"
> +#include "qda_rpmsg.h"
>
> /**
> * qda_ioctl_query() - Query DSP device information
> @@ -83,3 +86,83 @@ int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void
> *data, struct drm_fil
>
> return drm_gem_dumb_map_offset(file_priv, dev, args->handle,
> &args->offset);
> }
> +
> +static int qda_fastrpc_invoke(u32 type, struct drm_device *dev, void *data,
> + struct drm_file *file_priv)
> +{
> + struct qda_file_priv *qda_file_priv = file_priv->driver_priv;
> + struct qda_dev *qdev = qda_file_priv->qda_dev;
> + struct qda_fastrpc_invoke_ctx *ctx;
> + struct drm_gem_object *gem_obj;
> + struct qda_msg msg;
> + size_t hdr_size;
> + int err;
> +
> + ctx = qda_fastrpc_context_alloc(qdev);
> + if (IS_ERR(ctx))
> + return PTR_ERR(ctx);
> +
> + ctx->type = type;
> + ctx->file_priv = file_priv;
> + ctx->remote_session_id = qda_file_priv->remote_session_id;
> +
> + err = qda_fastrpc_prepare_args(ctx, data);
> + if (err)
> + goto err_context_free;
> +
> + hdr_size = qda_fastrpc_get_header_size(ctx);
> +
> + gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr, hdr_size,
> file_priv);
> + if (IS_ERR(gem_obj)) {
> + err = PTR_ERR(gem_obj);
> + goto err_context_free;
> + }
> +
> + ctx->msg_gem_obj = to_qda_gem_obj(gem_obj);
> +
> + err = qda_fastrpc_invoke_pack(ctx, &msg);
> + if (err)
> + goto err_context_free;
> +
> + err = qda_rpmsg_send_msg(qdev, &msg);
> + if (err)
> + goto err_context_free;
> +
> + err = qda_rpmsg_wait_for_rsp(ctx);
> + if (err)
> + goto err_context_free;
> +
> + /* Ensure the results written by the DSP are visible before reading */
> + dma_rmb();
> +
> + err = ctx->retval;
> + if (err) {
> + drm_dbg_driver(dev, "DSP returned status 0x%x for type %u\n",
> + ctx->retval, type);
> + goto err_context_free;
> + }
> +
> + qda_fastrpc_cleanup_handlelist(ctx);
> +
> + kref_put(&ctx->refcount, qda_fastrpc_context_free);
> +
> + return 0;
> +
> +err_context_free:
> + kref_put(&ctx->refcount, qda_fastrpc_context_free);
> +
> + return err;
> +}
> +
> +/**
> + * qda_ioctl_invoke() - Perform a dynamic FastRPC method invocation
> + * @dev: DRM device structure
> + * @data: User-space data (struct qda_invoke_args)
> + * @file_priv: DRM file private data
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file
> *file_priv)
> +{
> + return qda_fastrpc_invoke(FASTRPC_RMID_INVOKE_DYNAMIC, dev, data,
> file_priv);
> +}
> diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
> index d1cbbfb6d965..3bb9cfd98370 100644
> --- a/drivers/accel/qda/qda_ioctl.h
> +++ b/drivers/accel/qda/qda_ioctl.h
> @@ -11,5 +11,6 @@
> 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);
>
> #endif /* __QDA_IOCTL_H__ */
> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 64bf503106d9..7a5e0d3e20ca 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
> @@ -1,12 +1,17 @@
> // SPDX-License-Identifier: GPL-2.0-only
> // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> +#include <linux/completion.h>
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/rpmsg.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> #include <drm/drm_print.h>
>
> #include "qda_cb.h"
> #include "qda_drv.h"
> +#include "qda_fastrpc.h"
> +#include "qda_rpmsg.h"
>
> static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device
> *rpdev)
> {
> @@ -23,11 +28,92 @@ static struct qda_dev
> *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
> return qdev;
> }
>
> +/**
> + * qda_rpmsg_send_msg() - Send a packed invocation to the remote processor
> + * @qdev: QDA device structure
> + * @msg: Packed message to send
> + *
> + * Takes a reference on the invocation context on behalf of the response
> + * callback, which keeps the context alive until the DSP has replied.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_rpmsg_send_msg(struct qda_dev *qdev, struct qda_msg *msg)
> +{
> + struct qda_fastrpc_invoke_ctx *ctx = msg->fastrpc_ctx;
> + int ret, idx;
> +
> + if (!drm_dev_enter(&qdev->drm_dev, &idx))
> + return -ENODEV;
> +
> + if (!qdev->rpdev) {
> + ret = -ENODEV;
> + goto out_exit;
> + }
> +
> + kref_get(&ctx->refcount);
> +
> + ret = rpmsg_send(qdev->rpdev->ept, &msg->fastrpc, sizeof(msg->fastrpc));
> + if (ret) {
> + drm_err_ratelimited(&qdev->drm_dev, "Failed to send rpmsg:
> %d\n", ret);
> + kref_put(&ctx->refcount, qda_fastrpc_context_free);
> + }
> +
> +out_exit:
> + drm_dev_exit(idx);
> +
> + return ret;
> +}
> +
> +/**
> + * qda_rpmsg_wait_for_rsp() - Wait for the DSP to answer an invocation
> + * @ctx: FastRPC invocation context
> + *
> + * Return: 0 once the response arrived, or -ERESTARTSYS if a signal
> + * interrupted the wait
> + */
> +int qda_rpmsg_wait_for_rsp(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> + return wait_for_completion_interruptible(&ctx->work);
> +}
> +
> static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
> void *priv, u32 src)
> {
> - /* Placeholder: responses will be dispatched here */
> - return 0;
> + struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
> + struct qda_invoke_rsp *resp_msg = data;
> + struct qda_fastrpc_invoke_ctx *ctx;
> + int ret = 0, idx;
> +
> + if (!qdev)
> + return -ENODEV;
> +
> + if (!drm_dev_enter(&qdev->drm_dev, &idx))
> + return -ENODEV;
> +
> + if (len < sizeof(*resp_msg)) {
> + ret = -EINVAL;
> + goto out_exit;
> + }
> +
> + ctx = xa_load(&qdev->ctx_xa, resp_msg->ctx >> 4);
> + if (!ctx) {
> + drm_dbg_driver(&qdev->drm_dev, "No context matches response
> 0x%llx\n",
> + resp_msg->ctx);
> + ret = -ENOENT;
> + goto out_exit;
> + }
> +
> + ctx->retval = resp_msg->retval;
> + complete(&ctx->work);
> +
> + /* Release the reference taken by qda_rpmsg_send_msg() */
> + kref_put(&ctx->refcount, qda_fastrpc_context_free);
> +
> +out_exit:
> + drm_dev_exit(idx);
> +
> + return ret;
> }
>
> static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
> @@ -41,6 +127,7 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
> */
> drm_dev_unplug(&qdev->drm_dev);
> qdev->rpdev = NULL;
> + qda_fastrpc_flush_pending(qdev);
> qda_cb_unpopulate(qdev);
> qda_deinit_device(qdev);
> }
> diff --git a/drivers/accel/qda/qda_rpmsg.h b/drivers/accel/qda/qda_rpmsg.h
> new file mode 100644
> index 000000000000..a70f4a80808d
> --- /dev/null
> +++ b/drivers/accel/qda/qda_rpmsg.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifndef __QDA_RPMSG_H__
> +#define __QDA_RPMSG_H__
> +
> +#include "qda_drv.h"
> +#include "qda_fastrpc.h"
> +
> +/**
> + * struct qda_invoke_rsp - Response structure for FastRPC invocations
> + */
> +struct qda_invoke_rsp {
> + /** @ctx: Invoke caller context for matching request/response */
> + u64 ctx;
> + /** @retval: Return value from the remote invocation */
> + int retval;
> +};
> +
> +/* RPMsg transport layer functions */
> +int qda_rpmsg_send_msg(struct qda_dev *qdev, struct qda_msg *msg);
> +int qda_rpmsg_wait_for_rsp(struct qda_fastrpc_invoke_ctx *ctx);
> +
> +#endif /* __QDA_RPMSG_H__ */
> diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
> index 1d699f00e7be..5cf0fec1d91c 100644
> --- a/include/uapi/drm/qda_accel.h
> +++ b/include/uapi/drm/qda_accel.h
> @@ -21,6 +21,8 @@ extern "C" {
> #define DRM_QDA_QUERY 0x00
> #define DRM_QDA_GEM_CREATE 0x01
> #define DRM_QDA_GEM_MMAP_OFFSET 0x02
> +/* Command numbers 0x03-0x06 reserved for INIT_ATTACH, INIT_CREATE, MAP,
> MUNMAP */
WHY?
> +#define DRM_QDA_REMOTE_INVOKE 0x07
>
> /*
> * QDA IOCTL definitions
> @@ -35,6 +37,8 @@ extern "C" {
> struct drm_qda_gem_create)
> #define DRM_IOCTL_QDA_GEM_MMAP_OFFSET DRM_IOWR(DRM_COMMAND_BASE +
> DRM_QDA_GEM_MMAP_OFFSET, \
> struct drm_qda_gem_mmap_offset)
> +#define DRM_IOCTL_QDA_REMOTE_INVOKE DRM_IOWR(DRM_COMMAND_BASE +
> DRM_QDA_REMOTE_INVOKE, \
> + struct drm_qda_invoke_args)
>
> /* Query type definitions for drm_qda_query */
> #define QDA_QUERY_DSP_NAME 1
> @@ -84,6 +88,44 @@ struct drm_qda_gem_mmap_offset {
> __u32 pad;
> };
>
> +/**
> + * struct drm_qda_fastrpc_invoke_args - FastRPC invocation argument
> descriptor
> + * @ptr: Pointer to argument data (user virtual address)
Why do you need it? Is handle + length not enough? If you want, specify
offset inside the GEM BO.
> + * @length: Length of the argument data in bytes
> + * @handle: GEM handle for buffer arguments; 0 for scalar arguments
> + * @attr: Argument attributes and flags
> + *
> + * This structure describes a single argument passed to a FastRPC invocation.
> + * Arguments can be either scalar values or buffer references (via GEM
> handle).
> + * Userspace must import any DMA-BUF fd to a GEM handle before populating
> + * this field; the driver never accepts DMA-BUF fds directly.
> + */
> +struct drm_qda_fastrpc_invoke_args {
> + __u64 ptr;
> + __u64 length;
> + __u32 handle;
> + __u32 attr;
> +};
> +
> +/**
> + * struct drm_qda_invoke_args - Dynamic FastRPC invocation parameters
> + * @handle: Remote handle to invoke on the DSP
> + * @sc: FastRPC scalars value encoding the number of in/out buffers
> + * @args: User-space pointer to array of drm_qda_fastrpc_invoke_args
> descriptors;
> + * the handle field in each entry must be a GEM handle (or 0 for
> + * inline scalar buffers). Userspace must import DMA-BUF fds to GEM
What is inline scalar buffer? I think, we agreed that everything is a
GEM BO.
> + * handles before passing them here.
I'd say this is useless. It adds no information. If the driver only
accepts GEM handles, why do you need to speak about DMA BUFs here?
> + *
> + * This structure is used with DRM_IOCTL_QDA_REMOTE_INVOKE to perform a
> + * dynamic remote procedure call on the DSP. The args pointer must reference
> + * an array of REMOTE_SCALARS_LENGTH(sc) drm_qda_fastrpc_invoke_args entries.
And how does the user know, what is REMOTE_SCALARS_LENGTH? Define it
here, please (and start with QDA).
> + */
> +struct drm_qda_invoke_args {
> + __u32 handle;
> + __u32 sc;
Can we place sc with __u8 inargs, __u8 outargs? Then there is no need to
play with the bytes and talk about REMOTE_SCALARS_LENGTH.
> + __u64 args;
__u64 inargs, __u64 outargs?
> +};
> +
> #if defined(__cplusplus)
> }
> #endif
>
> --
> 2.34.1
>
--
With best wishes
Dmitry