All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver
@ 2026-11-01 22:21 Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 01/25] accel/qda: Add QDA driver documentation Ekansh Gupta
                   ` (25 more replies)
  0 siblings, 26 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

This series adds QDA, a DRM accel driver for the Hexagon DSPs on
Qualcomm SoCs. It is meant to replace drivers/misc/fastrpc.c. It speaks
the same FastRPC protocol to the same DSP firmware over the same RPMsg
channel, and it binds the same device-tree nodes. It adds no new DT
binding or compatible.

Existing user space keeps working. The series also adds a separate
module, qda_fastrpc_shim, which serves the full /dev/fastrpc-* ioctl
interface on top of QDA. A distribution can therefore switch from
CONFIG_QCOM_FASTRPC to CONFIG_DRM_ACCEL_QDA without breaking FastRPC
users.

Why a new driver
================

drivers/misc/fastrpc.c predates the accel subsystem. As a miscdevice it
implements its own buffer tracking, buffer sharing and per-process
state, all of which DRM already provides as common, well-reviewed code.
It keeps buffers in driver-private per-file lists. It accepts raw
DMA-BUF fds, user pointers and inline data as invocation arguments. It
keys state on the calling process. Each of these has needed
driver-specific fixes over time, and none of the code is shared with
any other driver.

Converting fastrpc.c in place into an accel driver was discussed on v3.
The conclusion was to build the core cleanly on accel, and to keep the
historical interface in a separate shim module that can later be
deprecated and dropped.

What moving to accel gives us
=============================

Shared infrastructure instead of driver-private code
- GEM provides buffer lifetime, reference counting and per-file handle
  tables, in place of driver-private lists.
- DMA-BUF import goes through PRIME, the path used across DRM, rather
  than a FastRPC-specific fd lookup.
- Device registration, file handling and unplug follow the accel/DRM
  model. DSP-specific code is limited to the FastRPC protocol and the
  DSP memory model.
- The driver is maintained under accel and reviewed against its
  conventions, alongside the other compute accelerators.

Stronger isolation and a smaller attack surface
- Every buffer argument is a (GEM handle, offset, length) triplet,
  checked against the buffer size. The accel UAPI accepts no user
  pointers, raw fds or inline payloads, so the kernel never resolves
  arbitrary user addresses on the DSP's behalf.
- Each session owns one IOMMU context bank exclusively. Context banks
  are never shared, and a buffer that belongs to another session's
  context bank is rejected.
- No state is keyed on PID or TGID, so isolation follows the open file,
  not the process.
- Privileged operations (signed process creation, and Root, Sensors or
  Audio PD attach) require CAP_SYS_ADMIN. The check is made in one
  place and covers both the accel UAPI and the legacy interface.

More robust runtime behaviour
- Context IDs carry a sequence number, so a late or duplicate DSP
  response for a recycled ID cannot complete the wrong invocation.
- Waits are interruptible. An abandoned invocation keeps its buffers
  alive until the DSP has finished with them.
- When the DSP crashes or stops, pending invocations fail with -EPIPE
  and further ioctls fail with -ENODEV, instead of hanging or touching
  freed state.

A cleaner, documented UAPI
- The interface is documented in Documentation/accel/qda/qda.rst. It
  uses a consistent QDA_ namespace and rejects unknown query types and
  options, which leaves room to extend it without new ioctls.
- The handle/offset/length model carries no host addresses, which makes
  it a better basis for virtualised DSP devices than the legacy ioctls.

No flag day for users or distributions
- qda_fastrpc_shim keeps the existing /dev/fastrpc-* nodes and ioctls
  working on the same core, so current user space runs unmodified while
  it moves to the accel interface.
- Both interfaces share one implementation of the protocol, buffer and
  session logic, so a fix lands once and applies to both.
- Once fastrpc.c is removed, there is one FastRPC driver to maintain
  instead of two.

Backwards compatibility and the path to removing fastrpc.c
==========================================================

* qda_fastrpc_shim (CONFIG_DRM_ACCEL_QDA_FASTRPC_SHIM) binds to an
  auxiliary device that the core creates for each DSP domain. It
  registers the same /dev/fastrpc-<dsp> and /dev/fastrpc-<dsp>-secure
  nodes as drivers/misc/fastrpc.c, with the same secure/non-secure node
  rules, and translates the legacy ioctls onto QDA sessions. All
  protocol, buffer and session logic lives in the QDA core; the shim
  only handles the legacy ABI.

* The core is keyed on struct qda_session rather than struct drm_file,
  so a session can exist without a DRM file behind it. This removes the
  blocker reported on v3: there was no way to construct a drm_file from
  outside the DRM core.

* QDA and fastrpc bind the same compatibles and create the same device
  nodes, so they are mutually exclusive: QCOM_FASTRPC now depends on
  !DRM_ACCEL_QDA.

Shim coverage, against include/uapi/misc/fastrpc.h:

  FASTRPC_IOCTL_ALLOC_DMA_BUFF       supported
  FASTRPC_IOCTL_FREE_DMA_BUFF        -ENOTTY, as in fastrpc.c (buffers
                                     are released by closing the fd)
  FASTRPC_IOCTL_INVOKE               supported
  FASTRPC_IOCTL_INIT_CREATE          supported (ELF image passed by fd)
  FASTRPC_IOCTL_INIT_ATTACH          supported
  FASTRPC_IOCTL_INIT_ATTACH_SNS      supported
  FASTRPC_IOCTL_INIT_CREATE_STATIC   supported (Audio PD)
  FASTRPC_IOCTL_MMAP / MUNMAP        supported
  FASTRPC_IOCTL_MEM_MAP / MEM_UNMAP  supported
  FASTRPC_IOCTL_GET_DSP_INFO         supported
  FASTRPC_IOCTL_SET_OPTION           supported (poll mode)

Known behavioural differences from fastrpc.c:
- INIT_CREATE with the ELF image given as a bare user pointer instead
  of an fd fails with -EOPNOTSUPP, as there is no buffer to resolve.
- INIT_CREATE_STATIC ignores memlen. The Audio PD remote heap size
  always comes from the device tree.
- Mappings are tracked per open file. MUNMAP with a length other than
  the mapped one is refused, and so is mapping the same fd twice with
  MEM_MAP. Mappings still outstanding when the file is closed are
  unmapped on the DSP before their buffers are released.

Proposed plan: merge QDA together with the shim, then remove
drivers/misc/fastrpc.c, then deprecate the shim and eventually drop it
once user space has moved to the accel interface.

Design overview
===============

* Context banks: each "qcom,fastrpc-compute-cb" child node becomes a
  platform device created with of_platform_device_create(), which gives
  it its own IOMMU domain. No driver binds to it, and no custom bus or
  iommu_buses[] change is needed. The devices are created before the
  accel device is registered and destroyed after it is unplugged.

* Session isolation: each session (accel file or legacy file) gets one
  context bank to itself on its first allocation or import. If none is
  free, the operation fails with -EBUSY. A buffer that resolves to
  another session's context bank, such as a PRIME export imported back
  on the same device, is rejected.

* Memory: buffers are DMA-coherent GEM objects allocated from the
  session's context bank. Imported DMA-BUFs are mapped into the same
  context bank and must be contiguous in its IOVA space, because the
  DSP firmware takes a single base address per buffer. The DSP manages
  its own virtual address space, so drm_gpuvm is not used.

* Per-SoC data: the stream-ID position in DSP addresses, the DMA
  address width and poll-mode support come from OF match data on the
  two existing compatibles, "qcom,kaanapali-fastrpc" and
  "qcom,fastrpc".

* Invocations: context IDs are allocated from an xarray and carry a
  sequence number. Waits are interruptible; an abandoned invocation
  stays alive until the DSP answers or the channel goes away.

* Privilege: the CAP_SYS_ADMIN check sits in the shared session code,
  so it applies equally to both interfaces.

* Audio PD remote heap: the reserved-memory region and "qcom,vmids" are
  parsed at probe, and the region is assigned to the DSP VMs with
  qcom_scm_assign_mem(). It is handed to the DSP only on the first
  successful Audio PD attach; later attaches, for example after a
  daemon restart, reuse it. It is returned to HLOS when the channel
  goes away.

* Capabilities: DSP capability attributes are fetched once per device
  through the DSP utilities handle and cached. If the DSP does not
  implement the API, the query fails with -EOPNOTSUPP.

* Poll mode: a session can opt in to polling a completion word for
  invocations of dynamically loaded modules in a user PD. If the DSP
  has not answered within 10 ms, the driver falls back to waiting for
  the RPMsg response.

Changes in v4
=============

v3 was mistakenly posted as "v2"; this posting is numbered v4 to keep
the numbering consistent (Krzysztof).

Architecture
- Drop the qda-compute-cb custom bus and the iommu_buses[] patch.
  Context-bank devices are now plain platform devices created from the
  existing DT child nodes (Jason).
- Add in-kernel compatibility for the legacy /dev/fastrpc-* UABI as a
  separate module on the auxiliary bus, covering every legacy ioctl
  (Bjorn, Dmitry, Rob, Krzysztof).
- Bind the existing compatibles instead of proposing a new one, and
  make QCOM_FASTRPC and DRM_ACCEL_QDA mutually exclusive (Krzysztof,
  Bjorn).
- Explain the motivation, benefits, compatibility model and fastrpc.c
  removal plan in the cover letter (Bjorn, Krzysztof).

Memory and sessions
- Replace PID-based context-bank assignment with per-session
  assignment. Context banks are no longer shared (Christian, Rob,
  Dmitry).
- Size the context-bank registry from the DT count, and drop unused
  fields, NULL checks and stored counters (Dmitry).
- Reject buffers that resolve to another session's context bank, and
  check offset and length against the buffer size.
- Document the import contiguity requirement and the reason for it
  (Dmitry).

UAPI
- Buffer arguments are (handle, offset, length) only; the inline and
  DMA-BUF fd argument forms are gone (Dmitry).
- Use QDA_ prefixes throughout and document the scalars (sc) layout
  (Dmitry).
- DRM_IOCTL_QDA_QUERY rejects unknown query types; drop unneeded
  padding (Dmitry).
- Add DRM_IOCTL_QDA_REMOTE_MMAP and DRM_IOCTL_QDA_REMOTE_MUNMAP, with
  whole-buffer and attribute-based requests.
- Add DRM_IOCTL_QDA_REMOTE_SESSION_ATTACH for the Root, Sensors and
  Audio PDs, including the Audio PD remote heap (open item in v3).
- Add QDA_QUERY_DSP_CAPABILITIES to DRM_IOCTL_QDA_QUERY rather than a
  separate ioctl.
- Add DRM_IOCTL_QDA_SET_OPTION with QDA_SET_OPTION_POLL_MODE.
- Require CAP_SYS_ADMIN for signed process creation and PD attach
  (open item in v3).

Protocol
- Handle context-ID wraparound and stale responses with a sequence
  number (Dmitry).
- Make the response wait interruptible, and keep the invocation alive
  until the DSP is done with its buffers.
- Fail pending invocations cleanly when the DSP restarts.

Process and style
- Rewrite commit messages to explain why each change is needed rather
  than what it contains (Dmitry).
- Put the MAINTAINERS entry in the first patch and add each F: line
  together with the files it covers (Krzysztof).
- Use guard()/scoped_guard() for locking (Markus).
- Use the DRM driver debug category for debug messages.
- Add the missing newline at the end of drivers/accel/Makefile
  (Krzysztof).
- Reorganise the series so each patch adds one feature end to end, with
  the shim support for a feature following its accel UAPI patch.

Testing
=======

Tested on <PLATFORMS> with fastrpc_test from the open-source FastRPC
user-space library, built with the open-source QAIC IDL compiler. The
test was run both through the accel UAPI and, unmodified, through
qda_fastrpc_shim:

  fastrpc_test -d 3 -U 1 -t linux -a v68

User space
==========

- FastRPC library: https://github.com/qualcomm/fastrpc
  (accel support: https://github.com/qualcomm/fastrpc/tree/accel/staging)
- QAIC compiler:   https://github.com/qualcomm/QAIC

Patch organisation
==================

01      Documentation and MAINTAINERS
02      Driver skeleton and RPMsg binding; QCOM_FASTRPC depends on !QDA
03-04   Context-bank devices and per-session assignment
05      UAPI header and DSP name query
06-07   GEM buffer allocation and DMA-BUF import
08-09   Legacy ops table, auxiliary device and shim skeleton
10-14   Invocation: contexts, RPMsg send and receive, argument packing,
        invoke ioctl, invoke through the shim
15-16   DSP process creation and release, and through the shim
17-19   Remote mmap and munmap, and map/unmap through the shim
20-22   Root/Sensors PD attach, Audio PD attach with remote heap, and
        attach through the shim
23-24   DSP capability query, and through the shim
25      Poll mode for invocation completion (accel UAPI and shim)

Parts of this patch were written with the help of an AI coding assistant
(Claude, claude-sonnet-5) and reviewed by the author.

Previous versions
=================

v3 (posted as "v2"): https://lore.kernel.org/all/20260817-qda-v2-v2-0-69a02e9090d4@oss.qualcomm.com/
v2 (posted as "v1"): https://lore.kernel.org/all/20260519-qda-series-v1-0-b2d984c297f8@oss.qualcomm.com/
v1 (RFC):            https://lore.kernel.org/dri-devel/20260224-qda-firstpost-v1-0-fe46a9c1a046@oss.qualcomm.com/

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
Ekansh Gupta (25):
      accel/qda: Add QDA driver documentation
      accel/qda: Add initial QDA DRM accelerator driver
      accel/qda: Add compute context bank devices
      accel/qda: Assign a context bank to each session
      accel/qda: Add UAPI header and DSP query ioctl
      accel/qda: Add buffer allocation for the DSP
      accel/qda: Add DMA-BUF import
      accel/qda: Add legacy ops base and auxiliary device
      accel/qda: Add legacy FastRPC shim skeleton
      accel/qda: Add FastRPC invocation contexts
      accel/qda: Add RPMsg send and response dispatch
      accel/qda: Add invocation argument validation and packing
      accel/qda: Add the invocation ioctl and teardown on unplug
      accel/qda: Add invocation through the legacy FastRPC shim
      accel/qda: Add DSP process creation and release
      accel/qda: Add DSP process creation and release to the legacy shim
      accel/qda: Add remote memory mapping to the DSP address space
      accel/qda: Add remote memory unmapping from the DSP address space
      accel/qda: Add remote memory map and unmap to the legacy shim
      accel/qda: Add remote attach to root and sensors protection domains
      accel/qda: Add Audio protection domain attach and its remote heap
      accel/qda: Add remote session attach to the legacy shim
      accel/qda: Add DSP capability query
      accel/qda: Add legacy shim support for DSP capability query
      accel/qda: Add poll mode for invocation completion

 Documentation/accel/index.rst          |    1 +
 Documentation/accel/qda/index.rst      |   13 +
 Documentation/accel/qda/qda.rst        |  222 +++++
 MAINTAINERS                            |   11 +
 drivers/accel/Kconfig                  |    1 +
 drivers/accel/Makefile                 |    3 +-
 drivers/accel/qda/Kconfig              |   45 +
 drivers/accel/qda/Makefile             |   21 +
 drivers/accel/qda/qda_cb.c             |  129 +++
 drivers/accel/qda/qda_cb.h             |   29 +
 drivers/accel/qda/qda_drv.c            |  229 ++++++
 drivers/accel/qda/qda_drv.h            |  187 +++++
 drivers/accel/qda/qda_fastrpc.c        | 1417 ++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h        |  452 ++++++++++
 drivers/accel/qda/qda_fastrpc_shim.c   |  745 +++++++++++++++++
 drivers/accel/qda/qda_gem.c            |  146 ++++
 drivers/accel/qda/qda_gem.h            |   53 ++
 drivers/accel/qda/qda_ioctl.c          |  460 +++++++++++
 drivers/accel/qda/qda_ioctl.h          |   33 +
 drivers/accel/qda/qda_legacy_ops.c     |  212 +++++
 drivers/accel/qda/qda_legacy_ops.h     |   68 ++
 drivers/accel/qda/qda_memory_dma.c     |   85 ++
 drivers/accel/qda/qda_memory_dma.h     |   22 +
 drivers/accel/qda/qda_memory_manager.c |  213 +++++
 drivers/accel/qda/qda_memory_manager.h |   77 ++
 drivers/accel/qda/qda_prime.c          |  217 +++++
 drivers/accel/qda/qda_prime.h          |   29 +
 drivers/accel/qda/qda_rpmsg.c          |  322 ++++++++
 drivers/accel/qda/qda_rpmsg.h          |   26 +
 drivers/accel/qda/qda_soc.c            |   66 ++
 drivers/accel/qda/qda_soc.h            |   61 ++
 drivers/misc/Kconfig                   |    1 +
 include/uapi/drm/qda_accel.h           |  282 +++++++
 33 files changed, 5877 insertions(+), 1 deletion(-)
---
base-commit: 7649ba73f9e7e1e04d0b46213e39c79ae09dd55c
change-id: 20261102-v4-e8feb321263f

Best regards,
-- 
Ekansh Gupta <ekangupt@qti.qualcomm.com>


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

* [PATCH v4 01/25] accel/qda: Add QDA driver documentation
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
                   ` (24 subsequent siblings)
  25 siblings, 0 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

The QDA driver is intended to replace drivers/misc/fastrpc.c with a DRM
accel driver. Its design differs from the existing driver in ways that
reviewers need to see before reading the code: buffers are only ever
passed as GEM handles, each open file owns an IOMMU context bank, and
the legacy /dev/fastrpc-* interface is provided by a separate
compatibility module instead of by the core driver.

Add the documentation at the start of the series so that the following
patches can be reviewed against it. It describes the driver as the
series completes it, and so covers ioctls and modules that later
patches add.

The document also points to the open source user space, the FastRPC
library and the QAIC compiler, used to exercise the driver.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 Documentation/accel/index.rst     |   1 +
 Documentation/accel/qda/index.rst |  13 +++
 Documentation/accel/qda/qda.rst   | 222 ++++++++++++++++++++++++++++++++++++++
 MAINTAINERS                       |   9 ++
 4 files changed, 245 insertions(+)

diff --git a/Documentation/accel/index.rst b/Documentation/accel/index.rst
index cbc7d4c3876a..5901ea7f784c 100644
--- a/Documentation/accel/index.rst
+++ b/Documentation/accel/index.rst
@@ -10,4 +10,5 @@ Compute Accelerators
    introduction
    amdxdna/index
    qaic/index
+   qda/index
    rocket/index
diff --git a/Documentation/accel/qda/index.rst b/Documentation/accel/qda/index.rst
new file mode 100644
index 000000000000..013400cf9c25
--- /dev/null
+++ b/Documentation/accel/qda/index.rst
@@ -0,0 +1,13 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+==================================
+accel/qda Qualcomm DSP Accelerator
+==================================
+
+The QDA driver provides a DRM accel based interface for Qualcomm DSP offload.
+It uses the FastRPC protocol and integrates with DRM and GEM infrastructure
+for device and buffer management.
+
+.. toctree::
+
+   qda
diff --git a/Documentation/accel/qda/qda.rst b/Documentation/accel/qda/qda.rst
new file mode 100644
index 000000000000..4d81f6b413ff
--- /dev/null
+++ b/Documentation/accel/qda/qda.rst
@@ -0,0 +1,222 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=====================================
+Qualcomm DSP Accelerator (QDA) Driver
+=====================================
+
+Introduction
+============
+
+The QDA driver is a DRM accel driver for Qualcomm's Hexagon DSPs. It provides
+an interface for offloading workloads such as AI inference, computer vision,
+audio processing and sensor processing to the DSPs on Qualcomm SoCs. It
+speaks the FastRPC protocol to the DSP over RPMsg and uses the DRM GEM and
+PRIME infrastructure for buffer management.
+
+The driver binds to the device-tree nodes described by
+``Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml``.
+
+Relationship to the FastRPC Driver
+==================================
+
+Qualcomm SoCs currently offload DSP work through ``drivers/misc/fastrpc.c``,
+a misc character device driver that predates the DRM accel subsystem. QDA is
+intended to replace it.
+
+To keep existing user space working, the ``qda_fastrpc_shim`` module
+(``CONFIG_DRM_ACCEL_QDA_FASTRPC_SHIM``) registers the same
+``/dev/fastrpc-<dsp>`` (and, where applicable, ``/dev/fastrpc-<dsp>-secure``)
+device nodes and translates the legacy ioctls onto QDA sessions. The shim
+holds only the legacy ABI handling; the protocol, buffer and session logic
+all live in the QDA core.
+
+QDA and ``CONFIG_QCOM_FASTRPC`` bind the same device-tree nodes and create
+the same device nodes, so they are mutually exclusive. Once the shim has
+reached parity with the legacy ABI, ``drivers/misc/fastrpc.c`` is to be
+removed.
+
+The shim keeps the legacy secure and non-secure node model. A DSP domain is
+secure unless its device-tree node has ``qcom,non-secure-domain``. ADSP, MDSP
+and SDSP get a single node, ``/dev/fastrpc-<dsp>`` or
+``/dev/fastrpc-<dsp>-secure`` according to the domain. CDSP and GDSP, which
+support unsigned protection domains, always get both nodes. On a secure
+domain, a client that opened the non-secure node may only create an unsigned
+protection domain, and only on a DSP that supports them. As in the legacy
+driver, access to either node is controlled by the node's file permissions.
+
+Architecture
+============
+
+The QDA driver consists of the following blocks:
+
+1.  **Core driver** (``qda_drv``): device registration, per-open session
+    state and DRM accel integration.
+2.  **Transport** (``qda_rpmsg``): binds to the DSP's RPMsg channel and sends
+    and receives FastRPC messages.
+3.  **SoC data** (``qda_soc``): per-SoC parameters such as the position of
+    the stream ID in a DSP address and the supported DMA address width.
+4.  **Context banks** (``qda_cb``): creates a platform device for each
+    ``qcom,fastrpc-compute-cb`` child node of the DSP's device-tree node,
+    using ``of_platform_device_create()``. Registering the device is what
+    gives it its IOMMU domain, so no driver has to bind to it. They are
+    created before the accel device is registered and destroyed after it is
+    unplugged, so user space can never reach a context bank that does not
+    exist.
+5.  **Memory manager** (``qda_memory_manager``, ``qda_memory_dma``): tracks
+    the context banks and allocates DMA-coherent memory from the one
+    assigned to a session.
+6.  **GEM** (``qda_gem``, ``qda_prime``): GEM buffer objects, mmap and PRIME
+    import.
+7.  **FastRPC core** (``qda_fastrpc``): argument marshalling, invocation
+    contexts and the DSP process management calls.
+8.  **Legacy interface** (``qda_legacy_ops``, ``qda_fastrpc_shim``): the
+    compatibility layer described above.
+
+Memory Management
+=================
+
+Buffers are GEM objects backed by DMA-coherent memory allocated through the
+session's context bank device, so their DMA address is an address in that
+context bank's SMMU address space.
+
+A session is one open file: either an ``/dev/accel/accel*`` file or, with
+the shim, a ``/dev/fastrpc-*`` one. It is assigned one context bank,
+exclusively, on its first buffer allocation or import. If no context bank is
+free, the operation fails with ``-EBUSY``. Context banks are not shared
+between sessions, so one session cannot reach another's buffers through an
+address the DSP accepts.
+
+Buffer arguments to every ioctl are identified by a GEM handle together with
+an offset and a length within that buffer. No ioctl takes a user pointer to
+buffer contents or a DMA-BUF file descriptor. To use a buffer from another
+driver, user space imports its DMA-BUF with ``DRM_IOCTL_PRIME_FD_TO_HANDLE``
+first. Imported buffers must be contiguous in the context bank's address
+space, because the DSP is given a single base address per buffer; other
+imports are rejected.
+
+The DSP manages its own virtual address space. The AP side only maps buffers
+into the SMMU context bank through the DMA API, so QDA does not use
+``drm_gpuvm``.
+
+User-Space API
+==============
+
+The driver exposes the following ioctls, defined in
+``include/uapi/drm/qda_accel.h``:
+
+``DRM_IOCTL_QDA_QUERY``
+    Query a property of the DSP selected by ``query_type``:
+    ``QDA_QUERY_DSP_NAME`` returns the domain name (for example ``cdsp``),
+    which is how user space picks the right ``/dev/accel/accel*`` node, and
+    ``QDA_QUERY_DSP_CAPABILITIES`` returns the value of the DSP capability
+    selected by ``attribute_id`` (cached per-device after the first
+    successful query).
+
+``DRM_IOCTL_QDA_GEM_CREATE``
+    Allocate a GEM buffer object and return its handle.
+
+``DRM_IOCTL_QDA_GEM_MMAP_OFFSET``
+    Return the offset to pass to ``mmap()`` on the accel file descriptor to
+    map a GEM buffer object. Imported buffers cannot be mapped this way.
+
+``DRM_IOCTL_QDA_REMOTE_SESSION_CREATE``
+    Create a user process on the DSP from an ELF image given as a GEM handle.
+    Creating a signed process domain (``attrs`` without
+    ``QDA_INIT_ATTR_UNSIGNED_MODULE``) requires ``CAP_SYS_ADMIN``.
+
+``DRM_IOCTL_QDA_REMOTE_MMAP`` / ``DRM_IOCTL_QDA_REMOTE_MUNMAP``
+    Create or remove a persistent mapping of a buffer in the DSP's address
+    space. ``request`` selects between mapping a whole buffer, optionally at
+    a hinted address (``QDA_REQUEST_MMAP``, ``QDA_REQUEST_MUNMAP``), and
+    mapping a region with SMMU attributes (``QDA_REQUEST_MEM_MAP``,
+    ``QDA_REQUEST_MEM_UNMAP``). An unmap must use the same request the
+    mapping was made with.
+
+    A mapping does not keep the buffer alive. Its GEM handle has to stay
+    open for as long as the DSP uses the mapping, and must only be closed
+    after the mapping has been removed.
+
+``DRM_IOCTL_QDA_REMOTE_INVOKE``
+    Call a method on the DSP. This is the main execution primitive.
+
+    The scalars value ``sc`` selects the method and describes the arguments:
+    bits 28..24 are the method index, bits 23..16 the number of input
+    buffers, bits 15..8 the number of output buffers, bits 7..4 the number
+    of input handles and bits 3..0 the number of output handles. ``args``
+    points to an array with one entry per argument, in that order, each
+    naming a buffer by GEM handle together with an offset and a length
+    within it.
+
+    The handles must stay open for the duration of the call. Buffers are
+    given to the DSP by their SMMU addresses, so they do not need a prior
+    ``REMOTE_MMAP``; use ``REMOTE_MMAP`` for buffers the DSP keeps using
+    across calls.
+
+``DRM_IOCTL_QDA_REMOTE_SESSION_ATTACH``
+    Attach the session to an existing protection domain instead of creating
+    a user process: ``QDA_ATTACH_ROOT_PD``, ``QDA_ATTACH_AUDIO_PD`` or
+    ``QDA_ATTACH_SENSORS_PD``. Requires ``CAP_SYS_ADMIN``, and fails with
+    ``-EBUSY`` if a process has already been created on the session. The
+    first Audio PD attach on a device additionally hands the DSP the remote
+    heap reserved for it in the device tree; later attaches reuse it.
+
+``DRM_IOCTL_QDA_SET_OPTION``
+    Set a session option. ``QDA_SET_OPTION_POLL_MODE`` switches a dynamic
+    invocation in a user PD between waiting for an interrupt-based reply and
+    polling shared memory for completion, trading CPU cycles for lower
+    round-trip latency. Rejected with ``-EOPNOTSUPP`` on DSPs that do not
+    support polling.
+
+Usage Example
+=============
+
+A typical lifecycle for a user-space application:
+
+1.  **Discovery**: open ``/dev/accel/accel*`` and use
+    ``DRM_IOCTL_QDA_QUERY`` to find the node for the wanted DSP domain.
+2.  **Initialization**: call ``DRM_IOCTL_QDA_REMOTE_SESSION_CREATE`` to
+    create a process on the DSP.
+3.  **Memory**: allocate buffers with ``DRM_IOCTL_QDA_GEM_CREATE``, or import
+    DMA-BUFs from other drivers with ``DRM_IOCTL_PRIME_FD_TO_HANDLE``.
+4.  **Execution**: use ``DRM_IOCTL_QDA_REMOTE_INVOKE`` to run functions on
+    the DSP.
+5.  **Cleanup**: close the file descriptor. The DSP process is released and
+    the session's buffers and context bank are freed.
+
+DSP Restart
+===========
+
+When the DSP's RPMsg channel goes away, for example because the remote
+processor is stopped or crashes, invocations still waiting for a reply fail
+with ``-EPIPE`` and later ioctls fail with ``-ENODEV``. The DSP comes back
+as a new accel device, possibly with a different minor number, so user space
+has to close the file descriptor and repeat discovery.
+
+Debugging
+=========
+
+The driver's debug messages use the DRM driver debug category. Enable them
+with:
+
+.. code-block:: bash
+
+    echo 0x2 > /sys/module/drm/parameters/debug
+
+or, where ``drm`` is built in, with ``drm.debug=0x2`` on the kernel command
+line.
+
+Testing
+=======
+
+The driver is exercised with the ``fastrpc_test`` utility from the
+open-source FastRPC user-space library, built with the open-source QAIC IDL
+compiler:
+
+* FastRPC library: https://github.com/qualcomm/fastrpc
+* QAIC compiler: https://github.com/qualcomm/QAIC
+
+For example, to run the tests on the CDSP with an unsigned process domain:
+
+.. code-block:: bash
+
+    fastrpc_test -d 3 -U 1 -t linux -a v68
diff --git a/MAINTAINERS b/MAINTAINERS
index aaf2825ac945..6803c60cbc64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22973,6 +22973,15 @@ S:	Supported
 F:	Documentation/devicetree/bindings/crypto/qcom-qce.yaml
 F:	drivers/crypto/qce/
 
+QUALCOMM DSP ACCELERATOR (QDA) DRIVER
+M:	Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
+M:	Srinivas Kandagatla <srini@kernel.org>
+L:	linux-arm-msm@vger.kernel.org
+L:	dri-devel@lists.freedesktop.org
+S:	Supported
+T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/accel/qda/
+
 QUALCOMM EMAC GIGABIT ETHERNET DRIVER
 M:	Timur Tabi <timur@kernel.org>
 L:	netdev@vger.kernel.org

-- 
2.34.1


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

* [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 01/25] accel/qda: Add QDA driver documentation Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:39   ` sashiko-bot
  2026-11-02  8:42   ` Krzysztof Kozlowski
  2026-11-01 22:21 ` [PATCH v4 03/25] accel/qda: Add compute context bank devices Ekansh Gupta
                   ` (23 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Qualcomm SoCs offload work to Hexagon DSPs today through
drivers/misc/fastrpc.c, a misc character device driver written before
the DRM accel subsystem existed. QDA is a new driver for the same
hardware, built around GEM and DMA-BUF instead of the ad hoc buffer
tracking the legacy driver has to do on its own, and exposed through
/dev/accel/accelN like other compute accelerators.

This patch lays the skeleton: a drm_driver with DRIVER_COMPUTE_ACCEL,
per-session state that later patches share between the native UAPI and
the legacy compatibility shim, and an rpmsg binding that brings the
device up when the DSP's RPMsg channel appears. The DSP domain name
comes from the "label" property already used on this node, since that
is how the platform identifies which Hexagon instance a given channel
talks to.

Both compatibles the binding defines are matched, so the driver probes
on every platform the legacy driver supports. The glymur, hawi, maili
and nord nodes name "qcom,kaanapali-fastrpc" as their fallback
compatible and so are matched by that entry. The DSP parameters that
differ between these platforms - the stream ID position in a
DSP-visible address, the DMA address width, and whether the firmware
supports invoke completion polling - are added as match data together
with the code that uses them, since the values are only meaningful
next to it.

The rpmsg binding targets the same compatibles that
drivers/misc/fastrpc.c binds to, because both drivers talk to the same
physical DSP behind the same RPMsg channel. Two rpmsg_drivers cannot
share a device, and driver_register() refuses two drivers with the same
name on one bus, so drivers/misc/Kconfig now makes QCOM_FASTRPC depend
on !DRM_ACCEL_QDA: a given kernel build contains one or the other. The
dependency is expressed on the legacy symbol so that enabling QDA is
what turns fastrpc off, rather than the reverse.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 MAINTAINERS                   |  1 +
 drivers/accel/Kconfig         |  1 +
 drivers/accel/Makefile        |  3 +-
 drivers/accel/qda/Kconfig     | 27 +++++++++++++
 drivers/accel/qda/Makefile    | 10 +++++
 drivers/accel/qda/qda_drv.c   | 84 +++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_drv.h   | 65 +++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_rpmsg.c | 91 +++++++++++++++++++++++++++++++++++++++++++
 drivers/misc/Kconfig          |  1 +
 9 files changed, 282 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6803c60cbc64..77687a35ac13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22981,6 +22981,7 @@ L:	dri-devel@lists.freedesktop.org
 S:	Supported
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/accel/qda/
+F:	drivers/accel/qda/
 
 QUALCOMM EMAC GIGABIT ETHERNET DRIVER
 M:	Timur Tabi <timur@kernel.org>
diff --git a/drivers/accel/Kconfig b/drivers/accel/Kconfig
index bdf48ccafcf2..74ac0f71bc9d 100644
--- a/drivers/accel/Kconfig
+++ b/drivers/accel/Kconfig
@@ -29,6 +29,7 @@ source "drivers/accel/ethosu/Kconfig"
 source "drivers/accel/habanalabs/Kconfig"
 source "drivers/accel/ivpu/Kconfig"
 source "drivers/accel/qaic/Kconfig"
+source "drivers/accel/qda/Kconfig"
 source "drivers/accel/rocket/Kconfig"
 
 endif
diff --git a/drivers/accel/Makefile b/drivers/accel/Makefile
index 1d3a7251b950..17686a592900 100644
--- a/drivers/accel/Makefile
+++ b/drivers/accel/Makefile
@@ -5,4 +5,5 @@ obj-$(CONFIG_DRM_ACCEL_ARM_ETHOSU)	+= ethosu/
 obj-$(CONFIG_DRM_ACCEL_HABANALABS)	+= habanalabs/
 obj-$(CONFIG_DRM_ACCEL_IVPU)		+= ivpu/
 obj-$(CONFIG_DRM_ACCEL_QAIC)		+= qaic/
-obj-$(CONFIG_DRM_ACCEL_ROCKET)		+= rocket/
\ No newline at end of file
+obj-$(CONFIG_DRM_ACCEL_QDA)		+= qda/
+obj-$(CONFIG_DRM_ACCEL_ROCKET)		+= rocket/
diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
new file mode 100644
index 000000000000..e2f3f01016c6
--- /dev/null
+++ b/drivers/accel/qda/Kconfig
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Qualcomm DSP accelerator driver
+#
+
+config DRM_ACCEL_QDA
+	tristate "Qualcomm DSP accelerator"
+	depends on DRM_ACCEL
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on RPMSG
+	help
+	  Enables the DRM-based accelerator driver for Qualcomm's Hexagon DSPs.
+	  This driver provides a standardized interface for offloading computational
+	  tasks to the DSP, including audio processing, sensor offload, computer
+	  vision, and AI inference workloads.
+
+	  The driver supports all DSP domains (ADSP, CDSP, SDSP, GDSP) and
+	  implements the FastRPC protocol for communication between the application
+	  processor and DSP. It integrates with the Linux kernel's Compute
+	  Accelerators subsystem (drivers/accel/) and is intended to replace the
+	  FastRPC driver in drivers/misc/. Both drivers bind the same device tree
+	  nodes, so only one of them can be built.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called qda.
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
new file mode 100644
index 000000000000..dbe809067a8b
--- /dev/null
+++ b/drivers/accel/qda/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for Qualcomm DSP accelerator driver
+#
+
+obj-$(CONFIG_DRM_ACCEL_QDA)	:= qda.o
+
+qda-y := \
+	qda_drv.o \
+	qda_rpmsg.o
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
new file mode 100644
index 000000000000..f4430332ff6e
--- /dev/null
+++ b/drivers/accel/qda/qda_drv.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <drm/drm_accel.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_print.h>
+
+#include "qda_drv.h"
+
+static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
+{
+	struct qda_session *session;
+
+	session = kzalloc_obj(*session);
+	if (!session)
+		return ERR_PTR(-ENOMEM);
+
+	session->qda_dev = qdev;
+
+	return session;
+}
+
+static int qda_open(struct drm_device *dev, struct drm_file *file)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(dev);
+	struct qda_session *session;
+
+	session = qda_session_alloc(qdev);
+	if (IS_ERR(session))
+		return PTR_ERR(session);
+
+	session->file = file;
+	file->driver_priv = session;
+
+	return 0;
+}
+
+static void qda_postclose(struct drm_device *dev, struct drm_file *file)
+{
+	struct qda_session *session = file->driver_priv;
+
+	kfree(session);
+}
+
+DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
+
+static const struct drm_driver qda_drm_driver = {
+	.driver_features = DRIVER_COMPUTE_ACCEL,
+	.fops = &qda_accel_fops,
+	.open = qda_open,
+	.postclose = qda_postclose,
+	.name = QDA_DRIVER_NAME,
+	.desc = "Qualcomm DSP Accelerator Driver",
+};
+
+struct qda_dev *qda_alloc_device(struct device *dev)
+{
+	struct qda_dev *qdev;
+
+	qdev = devm_drm_dev_alloc(dev, &qda_drm_driver, struct qda_dev, drm_dev);
+	if (IS_ERR(qdev))
+		return ERR_CAST(qdev);
+
+	return qdev;
+}
+
+int qda_register_device(struct qda_dev *qdev)
+{
+	int ret;
+
+	ret = drm_dev_register(&qdev->drm_dev, 0);
+	if (ret)
+		drm_err(&qdev->drm_dev, "Failed to register DRM device: %d\n", ret);
+
+	return ret;
+}
+
+MODULE_AUTHOR("Qualcomm AI Infra Team");
+MODULE_DESCRIPTION("Qualcomm DSP Accelerator Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
new file mode 100644
index 000000000000..dd3345b6a5ad
--- /dev/null
+++ b/drivers/accel/qda/qda_drv.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_DRV_H__
+#define __QDA_DRV_H__
+
+#include <linux/device.h>
+#include <linux/rpmsg.h>
+#include <linux/types.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+
+/* Driver identification */
+#define QDA_DRIVER_NAME "qda"
+
+/**
+ * struct qda_session - Per-open-file private data
+ *
+ * Allocated in qda_open() and stored in drm_file::driver_priv.
+ */
+struct qda_session {
+	/** @qda_dev: Back-pointer to device structure */
+	struct qda_dev *qda_dev;
+	/** @file: The drm_file this session belongs to */
+	struct drm_file *file;
+};
+
+/**
+ * struct qda_dev - Main device structure for QDA driver
+ *
+ * The DRM device is embedded as the first member so that container_of()
+ * can recover the qda_dev from any drm_device pointer.
+ */
+struct qda_dev {
+	/** @drm_dev: Embedded DRM device; recover via qda_dev_from_drm() */
+	struct drm_device drm_dev;
+	/** @rpdev: RPMsg device for communication with the remote processor */
+	struct rpmsg_device *rpdev;
+	/** @dev: Underlying Linux device */
+	struct device *dev;
+	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
+	const char *dsp_name;
+};
+
+/**
+ * qda_dev_from_drm - Recover qda_dev from an embedded drm_device pointer
+ * @dev: Pointer to the embedded drm_device
+ *
+ * Return: Pointer to the enclosing qda_dev.
+ */
+static inline struct qda_dev *qda_dev_from_drm(struct drm_device *dev)
+{
+	return container_of(dev, struct qda_dev, drm_dev);
+}
+
+/* Device allocation (uses devm_drm_dev_alloc internally) */
+struct qda_dev *qda_alloc_device(struct device *dev);
+
+/* Core device lifecycle */
+int qda_register_device(struct qda_dev *qdev);
+
+#endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
new file mode 100644
index 000000000000..6bd977e8d68a
--- /dev/null
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/rpmsg.h>
+#include <drm/drm_print.h>
+
+#include "qda_drv.h"
+
+/*
+ * Both compatibles the binding defines are matched here. The glymur, hawi,
+ * maili and nord nodes name "qcom,kaanapali-fastrpc" as their fallback
+ * compatible and so are matched by that entry. Per-SoC DSP parameters are
+ * attached as match data by a later patch, once there is something that
+ * consumes them.
+ */
+static const struct of_device_id qda_rpmsg_of_match[] = {
+	{ .compatible = "qcom,kaanapali-fastrpc" },
+	{ .compatible = "qcom,fastrpc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qda_rpmsg_of_match);
+
+static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
+{
+	struct qda_dev *qdev;
+
+	qdev = qda_alloc_device(&rpdev->dev);
+	if (IS_ERR(qdev))
+		return qdev;
+
+	qdev->dev = &rpdev->dev;
+	qdev->rpdev = rpdev;
+	dev_set_drvdata(&rpdev->dev, qdev);
+
+	return qdev;
+}
+
+/*
+ * The rpmsg core requires a callback, so one is registered from the start.
+ * Responses are dispatched here once there are invocations to answer.
+ */
+static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+			void *priv, u32 src)
+{
+	return 0;
+}
+
+static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+	struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
+
+	/*
+	 * Unplug before clearing @rpdev: once there are ioctls that reach the
+	 * DSP, this is what stops a new one from starting and waits for the
+	 * ones already running, so that none of them sees a transport that has
+	 * gone away.
+	 */
+	drm_dev_unplug(&qdev->drm_dev);
+	qdev->rpdev = NULL;
+}
+
+static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+	struct qda_dev *qdev;
+	int ret;
+
+	qdev = qda_rpmsg_alloc_and_init_qdev(rpdev);
+	if (IS_ERR(qdev))
+		return PTR_ERR(qdev);
+
+	ret = of_property_read_string(rpdev->dev.of_node, "label", &qdev->dsp_name);
+	if (ret) {
+		dev_err(&rpdev->dev, "Missing 'label' property in DT node: %d\n", ret);
+		return ret;
+	}
+
+	return qda_register_device(qdev);
+}
+
+static struct rpmsg_driver qda_rpmsg_driver = {
+	.probe = qda_rpmsg_probe,
+	.remove = qda_rpmsg_remove,
+	.callback = qda_rpmsg_cb,
+	.drv = {
+		.name = QDA_DRIVER_NAME,
+		.of_match_table = qda_rpmsg_of_match,
+	},
+};
+
+module_rpmsg_driver(qda_rpmsg_driver);
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c40e89cc5724..14b7bbaadcaa 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -269,6 +269,7 @@ config QCOM_FASTRPC
 	tristate "Qualcomm FastRPC"
 	depends on ARCH_QCOM || COMPILE_TEST
 	depends on RPMSG
+	depends on !DRM_ACCEL_QDA
 	select DMA_SHARED_BUFFER
 	select QCOM_SCM
 	help

-- 
2.34.1


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

* [PATCH v4 03/25] accel/qda: Add compute context bank devices
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 01/25] accel/qda: Add QDA driver documentation Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:52   ` sashiko-bot
  2026-11-02  8:43   ` Krzysztof Kozlowski
  2026-11-01 22:21 ` [PATCH v4 04/25] accel/qda: Assign a context bank to each session Ekansh Gupta
                   ` (22 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

The DSP does not access memory through a single IOMMU domain. Its
device tree node describes a set of context banks, one
"qcom,fastrpc-compute-cb" child node each, and every context bank has
its own SMMU address space. Later patches give each session a context
bank of its own so that sessions cannot reach each other's buffers, but
that needs a struct device per context bank to allocate from.

Create a platform device for each context bank node when the rpmsg
channel probes, and destroy them again when the channel goes away or
probe fails. Registering the device is what gives it an IOMMU group
and domain: the IOMMU core probes it from the platform bus notifier
when it is added, so no driver needs to bind to it. The devices are
created before the DRM device is registered, so user space can never
reach a context bank that does not exist yet. They are destroyed after
drm_dev_unplug(), which makes sure no ioctl is still running against
them.

The width of the address space the DSP can reach differs between SoCs
and DSPs: the CDSP on Kaanapali and the platforms that use it as their
fallback compatible can address 34 bits, everything else 32. Carry this
in struct qda_soc_data, selected through the OF match data of the two
compatibles the binding defines, and apply it to each context bank with
dma_set_mask(). All CDSP instances (cdsp, cdsp1, ...) share the CDSP
width, which is why the DSP name is matched by prefix. The remaining
per-SoC parameters are added together with the code that uses them.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Makefile    |  4 ++-
 drivers/accel/qda/qda_cb.c    | 76 +++++++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_cb.h    | 29 +++++++++++++++++
 drivers/accel/qda/qda_drv.c   |  2 ++
 drivers/accel/qda/qda_drv.h   |  6 ++++
 drivers/accel/qda/qda_rpmsg.c | 25 ++++++++++----
 drivers/accel/qda/qda_soc.c   | 31 ++++++++++++++++++
 drivers/accel/qda/qda_soc.h   | 30 +++++++++++++++++
 8 files changed, 196 insertions(+), 7 deletions(-)

diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index dbe809067a8b..4f2f236086a4 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -6,5 +6,7 @@
 obj-$(CONFIG_DRM_ACCEL_QDA)	:= qda.o
 
 qda-y := \
+	qda_cb.o \
 	qda_drv.o \
-	qda_rpmsg.o
+	qda_rpmsg.o \
+	qda_soc.o
diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
new file mode 100644
index 000000000000..afb0bb71a1f4
--- /dev/null
+++ b/drivers/accel/qda/qda_cb.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <drm/drm_print.h>
+#include "qda_cb.h"
+#include "qda_drv.h"
+#include "qda_soc.h"
+
+static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
+{
+	struct qda_cb_dev *entry;
+	struct platform_device *pdev;
+	u32 dma_bits;
+	int ret;
+
+	pdev = of_platform_device_create(cb_node, NULL, qdev->dev);
+	if (!pdev) {
+		drm_err(&qdev->drm_dev, "Failed to create CB device for %pOFn\n", cb_node);
+		return -ENODEV;
+	}
+
+	dma_bits = qda_soc_dma_addr_bits(qdev);
+	ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(dma_bits));
+	if (ret) {
+		drm_err(&qdev->drm_dev, "%u-bit DMA enable failed for %pOFn: %d\n",
+			dma_bits, cb_node, ret);
+		goto err_destroy;
+	}
+
+	entry = kzalloc_obj(*entry);
+	if (!entry) {
+		ret = -ENOMEM;
+		goto err_destroy;
+	}
+
+	entry->pdev = pdev;
+	list_add_tail(&entry->node, &qdev->cb_devs);
+
+	return 0;
+
+err_destroy:
+	of_platform_device_destroy(&pdev->dev, NULL);
+	return ret;
+}
+
+int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node)
+{
+	int ret;
+
+	for_each_available_child_of_node_scoped(parent_node, child) {
+		if (!of_device_is_compatible(child, "qcom,fastrpc-compute-cb"))
+			continue;
+
+		ret = qda_create_cb_device(qdev, child);
+		if (ret) {
+			qda_cb_unpopulate(qdev);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+void qda_cb_unpopulate(struct qda_dev *qdev)
+{
+	struct qda_cb_dev *entry, *tmp;
+
+	list_for_each_entry_safe(entry, tmp, &qdev->cb_devs, node) {
+		list_del(&entry->node);
+		of_platform_device_destroy(&entry->pdev->dev, NULL);
+		kfree(entry);
+	}
+}
diff --git a/drivers/accel/qda/qda_cb.h b/drivers/accel/qda/qda_cb.h
new file mode 100644
index 000000000000..070dc1de67a0
--- /dev/null
+++ b/drivers/accel/qda/qda_cb.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_CB_H__
+#define __QDA_CB_H__
+
+#include <linux/list.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include "qda_drv.h"
+
+/**
+ * struct qda_cb_dev - Bookkeeping for one compute context bank device
+ */
+struct qda_cb_dev {
+	/** @node: Entry in &qda_dev.cb_devs */
+	struct list_head node;
+	/** @pdev: Platform device created for the context bank node */
+	struct platform_device *pdev;
+};
+
+/* Create a device for each context bank child node of @parent_node */
+int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node);
+/* Destroy every context bank device created by qda_cb_populate() */
+void qda_cb_unpopulate(struct qda_dev *qdev);
+
+#endif /* __QDA_CB_H__ */
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index f4430332ff6e..6ecb6a966f68 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -65,6 +65,8 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 	if (IS_ERR(qdev))
 		return ERR_CAST(qdev);
 
+	INIT_LIST_HEAD(&qdev->cb_devs);
+
 	return qdev;
 }
 
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index dd3345b6a5ad..737232f7027f 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -7,11 +7,13 @@
 #define __QDA_DRV_H__
 
 #include <linux/device.h>
+#include <linux/list.h>
 #include <linux/rpmsg.h>
 #include <linux/types.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
+#include "qda_soc.h"
 
 /* Driver identification */
 #define QDA_DRIVER_NAME "qda"
@@ -41,6 +43,10 @@ struct qda_dev {
 	struct rpmsg_device *rpdev;
 	/** @dev: Underlying Linux device */
 	struct device *dev;
+	/** @cb_devs: Compute context-bank (CB) child devices */
+	struct list_head cb_devs;
+	/** @soc_data: Per-SoC DSP integration parameters */
+	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
 	const char *dsp_name;
 };
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 6bd977e8d68a..78b5f31974c1 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -5,18 +5,18 @@
 #include <linux/rpmsg.h>
 #include <drm/drm_print.h>
 
+#include "qda_cb.h"
 #include "qda_drv.h"
+#include "qda_soc.h"
 
 /*
  * Both compatibles the binding defines are matched here. The glymur, hawi,
  * maili and nord nodes name "qcom,kaanapali-fastrpc" as their fallback
- * compatible and so are matched by that entry. Per-SoC DSP parameters are
- * attached as match data by a later patch, once there is something that
- * consumes them.
+ * compatible and so are matched by that entry.
  */
 static const struct of_device_id qda_rpmsg_of_match[] = {
-	{ .compatible = "qcom,kaanapali-fastrpc" },
-	{ .compatible = "qcom,fastrpc" },
+	{ .compatible = "qcom,kaanapali-fastrpc", .data = &kaanapali_soc_data },
+	{ .compatible = "qcom,fastrpc", .data = &default_soc_data },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, qda_rpmsg_of_match);
@@ -58,6 +58,7 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
 	 */
 	drm_dev_unplug(&qdev->drm_dev);
 	qdev->rpdev = NULL;
+	qda_cb_unpopulate(qdev);
 }
 
 static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
@@ -69,13 +70,25 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
 	if (IS_ERR(qdev))
 		return PTR_ERR(qdev);
 
+	qdev->soc_data = of_device_get_match_data(&rpdev->dev);
+	if (!qdev->soc_data)
+		return -ENODEV;
+
 	ret = of_property_read_string(rpdev->dev.of_node, "label", &qdev->dsp_name);
 	if (ret) {
 		dev_err(&rpdev->dev, "Missing 'label' property in DT node: %d\n", ret);
 		return ret;
 	}
 
-	return qda_register_device(qdev);
+	ret = qda_cb_populate(qdev, rpdev->dev.of_node);
+	if (ret)
+		return ret;
+
+	ret = qda_register_device(qdev);
+	if (ret)
+		qda_cb_unpopulate(qdev);
+
+	return ret;
 }
 
 static struct rpmsg_driver qda_rpmsg_driver = {
diff --git a/drivers/accel/qda/qda_soc.c b/drivers/accel/qda/qda_soc.c
new file mode 100644
index 000000000000..323b620b8adb
--- /dev/null
+++ b/drivers/accel/qda/qda_soc.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/string.h>
+#include "qda_drv.h"
+#include "qda_soc.h"
+
+const struct qda_soc_data kaanapali_soc_data = {
+	.dma_addr_bits_cdsp = 34,
+	.dma_addr_bits_default = 32,
+};
+
+const struct qda_soc_data default_soc_data = {
+	.dma_addr_bits_cdsp = 32,
+	.dma_addr_bits_default = 32,
+};
+
+/**
+ * qda_soc_dma_addr_bits() - DMA address width of a device's context banks
+ * @qdev: QDA device
+ *
+ * All CDSP instances (cdsp, cdsp1, ...) share the CDSP width.
+ *
+ * Return: Number of DMA address bits the DSP can address on @qdev
+ */
+u32 qda_soc_dma_addr_bits(const struct qda_dev *qdev)
+{
+	if (!strncmp(qdev->dsp_name, "cdsp", 4))
+		return qdev->soc_data->dma_addr_bits_cdsp;
+
+	return qdev->soc_data->dma_addr_bits_default;
+}
diff --git a/drivers/accel/qda/qda_soc.h b/drivers/accel/qda/qda_soc.h
new file mode 100644
index 000000000000..b052964f11ae
--- /dev/null
+++ b/drivers/accel/qda/qda_soc.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_SOC_H__
+#define __QDA_SOC_H__
+
+#include <linux/types.h>
+
+struct qda_dev;
+
+/**
+ * struct qda_soc_data - Per-SoC DSP integration parameters
+ *
+ * Selected by OF match on the fastrpc node; see qda_rpmsg.c.
+ */
+struct qda_soc_data {
+	/** @dma_addr_bits_cdsp: DMA address width of CDSP context banks */
+	u32 dma_addr_bits_cdsp;
+	/** @dma_addr_bits_default: DMA address width of all other context banks */
+	u32 dma_addr_bits_default;
+};
+
+extern const struct qda_soc_data kaanapali_soc_data;
+extern const struct qda_soc_data default_soc_data;
+
+u32 qda_soc_dma_addr_bits(const struct qda_dev *qdev);
+
+#endif /* __QDA_SOC_H__ */

-- 
2.34.1


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

* [PATCH v4 04/25] accel/qda: Assign a context bank to each session
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (2 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 03/25] accel/qda: Add compute context bank devices Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:39   ` sashiko-bot
  2026-11-02  8:45   ` Krzysztof Kozlowski
  2026-11-01 22:21 ` [PATCH v4 05/25] accel/qda: Add UAPI header and DSP query ioctl Ekansh Gupta
                   ` (21 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Buffers a session gives to the DSP have to be mapped into one of the
DSP's context banks, and the DSP identifies them by an address in that
context bank's address space. If two sessions allocated from the same
context bank, each would be able to reach the other's buffers through
addresses the DSP accepts, so a context bank must not be shared.

Keep the context banks of a device in a registry sized to the number of
nodes found in the device tree, and hand each session one of them,
exclusively, until the session is released. A session that asks when
none is free gets -EBUSY rather than sharing. Nothing allocates from a
context bank yet; that comes with the DMA backend.

A context bank is reference counted and held both by the registry and
by the session that owns it. The rpmsg channel can go away while a file
is still open, which destroys the context bank devices, so the session
keeps the object alive until it closes rather than leaving a dangling
pointer behind.

qda_cb.c's cb_devs list and the new qda_memory_manager registry track
the same set of context banks but for different reasons, and keeping
them separate is deliberate. cb_devs exists to run the platform_device
create/destroy dance against the device tree; nothing there knows about
sessions. qda_memory_manager exists to answer a different question -
which context bank, if any, is free right now - and is the only place
that needs the per-session exclusivity rule from the second paragraph
above. It is deliberately kept independent of GEM: the context bank
registry has to be consulted (and a bank assigned) before any buffer
exists to attach a GEM helper to, and the same registry will grow to
hold each bank's IOMMU/DMA state once the DMA backend lands, which has
no natural home on a GEM object either. Folding the two together would
make qda_cb.c respond to session lifetime, or qda_memory_manager drive
of_platform_device_create()/destroy(), neither of which is its job.

Note that this inherits a property of the existing fastrpc driver: a
DSP has only a handful of context banks, so a process that opens the
device repeatedly can take all of them and keep others off the DSP.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Makefile             |   1 +
 drivers/accel/qda/qda_cb.c             |  45 ++++++++-
 drivers/accel/qda/qda_drv.c            |  30 +++++-
 drivers/accel/qda/qda_drv.h            |   7 ++
 drivers/accel/qda/qda_memory_manager.c | 176 +++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_memory_manager.h |  69 +++++++++++++
 drivers/accel/qda/qda_rpmsg.c          |  26 ++++-
 7 files changed, 349 insertions(+), 5 deletions(-)

diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 4f2f236086a4..1ca991e198ed 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -8,5 +8,6 @@ obj-$(CONFIG_DRM_ACCEL_QDA)	:= qda.o
 qda-y := \
 	qda_cb.o \
 	qda_drv.o \
+	qda_memory_manager.o \
 	qda_rpmsg.o \
 	qda_soc.o
diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
index afb0bb71a1f4..6f1cff1133f9 100644
--- a/drivers/accel/qda/qda_cb.c
+++ b/drivers/accel/qda/qda_cb.c
@@ -7,8 +7,44 @@
 #include <drm/drm_print.h>
 #include "qda_cb.h"
 #include "qda_drv.h"
+#include "qda_memory_manager.h"
 #include "qda_soc.h"
 
+static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev)
+{
+	struct qda_iommu_device *iommu_dev;
+	int ret;
+
+	iommu_dev = kzalloc_obj(*iommu_dev);
+	if (!iommu_dev)
+		return -ENOMEM;
+
+	iommu_dev->dev = cb_dev;
+	iommu_dev->qdev = qdev;
+
+	ret = qda_memory_manager_register_device(qdev->iommu_mgr, iommu_dev);
+	if (ret) {
+		drm_err(&qdev->drm_dev, "Failed to register context bank: %d\n", ret);
+		kfree(iommu_dev);
+		return ret;
+	}
+
+	dev_set_drvdata(cb_dev, iommu_dev);
+
+	return 0;
+}
+
+static void qda_cb_teardown_device(struct device *cb_dev)
+{
+	struct qda_iommu_device *iommu_dev = dev_get_drvdata(cb_dev);
+
+	if (!iommu_dev)
+		return;
+
+	qda_memory_manager_unregister_device(iommu_dev->qdev->iommu_mgr, iommu_dev);
+	dev_set_drvdata(cb_dev, NULL);
+}
+
 static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
 {
 	struct qda_cb_dev *entry;
@@ -30,10 +66,14 @@ static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_nod
 		goto err_destroy;
 	}
 
+	ret = qda_cb_setup_device(qdev, &pdev->dev);
+	if (ret)
+		goto err_destroy;
+
 	entry = kzalloc_obj(*entry);
 	if (!entry) {
 		ret = -ENOMEM;
-		goto err_destroy;
+		goto err_teardown;
 	}
 
 	entry->pdev = pdev;
@@ -41,6 +81,8 @@ static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_nod
 
 	return 0;
 
+err_teardown:
+	qda_cb_teardown_device(&pdev->dev);
 err_destroy:
 	of_platform_device_destroy(&pdev->dev, NULL);
 	return ret;
@@ -70,6 +112,7 @@ void qda_cb_unpopulate(struct qda_dev *qdev)
 
 	list_for_each_entry_safe(entry, tmp, &qdev->cb_devs, node) {
 		list_del(&entry->node);
+		qda_cb_teardown_device(&entry->pdev->dev);
 		of_platform_device_destroy(&entry->pdev->dev, NULL);
 		kfree(entry);
 	}
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 6ecb6a966f68..a99e23933f6b 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -39,13 +39,19 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
 	return 0;
 }
 
-static void qda_postclose(struct drm_device *dev, struct drm_file *file)
+static void qda_session_free(struct qda_session *session)
 {
-	struct qda_session *session = file->driver_priv;
+	if (session->assigned_iommu_dev)
+		qda_memory_manager_release_device(session->assigned_iommu_dev);
 
 	kfree(session);
 }
 
+static void qda_postclose(struct drm_device *dev, struct drm_file *file)
+{
+	qda_session_free(file->driver_priv);
+}
+
 DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
 
 static const struct drm_driver qda_drm_driver = {
@@ -70,6 +76,26 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 	return qdev;
 }
 
+int qda_init_device(struct qda_dev *qdev, int num_cbs)
+{
+	int ret;
+
+	qdev->iommu_mgr = devm_kzalloc(qdev->dev, sizeof(*qdev->iommu_mgr), GFP_KERNEL);
+	if (!qdev->iommu_mgr)
+		return -ENOMEM;
+
+	ret = qda_memory_manager_init(qdev->iommu_mgr, num_cbs);
+	if (ret)
+		drm_err(&qdev->drm_dev, "Failed to initialize memory manager: %d\n", ret);
+
+	return ret;
+}
+
+void qda_deinit_device(struct qda_dev *qdev)
+{
+	qda_memory_manager_exit(qdev->iommu_mgr);
+}
+
 int qda_register_device(struct qda_dev *qdev)
 {
 	int ret;
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 737232f7027f..c852d3b05310 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -13,6 +13,7 @@
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
+#include "qda_memory_manager.h"
 #include "qda_soc.h"
 
 /* Driver identification */
@@ -28,6 +29,8 @@ struct qda_session {
 	struct qda_dev *qda_dev;
 	/** @file: The drm_file this session belongs to */
 	struct drm_file *file;
+	/** @assigned_iommu_dev: Context bank owned by this session, or NULL */
+	struct qda_iommu_device *assigned_iommu_dev;
 };
 
 /**
@@ -45,6 +48,8 @@ struct qda_dev {
 	struct device *dev;
 	/** @cb_devs: Compute context-bank (CB) child devices */
 	struct list_head cb_devs;
+	/** @iommu_mgr: Registry of this device's context banks */
+	struct qda_memory_manager *iommu_mgr;
 	/** @soc_data: Per-SoC DSP integration parameters */
 	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
@@ -66,6 +71,8 @@ static inline struct qda_dev *qda_dev_from_drm(struct drm_device *dev)
 struct qda_dev *qda_alloc_device(struct device *dev);
 
 /* Core device lifecycle */
+int qda_init_device(struct qda_dev *qdev, int num_cbs);
+void qda_deinit_device(struct qda_dev *qdev);
 int qda_register_device(struct qda_dev *qdev);
 
 #endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_memory_manager.c b/drivers/accel/qda/qda_memory_manager.c
new file mode 100644
index 000000000000..87e8d3485e81
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_manager.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <drm/drm_print.h>
+#include "qda_drv.h"
+#include "qda_memory_manager.h"
+
+static void iommu_device_free(struct kref *ref)
+{
+	kfree(container_of(ref, struct qda_iommu_device, ref));
+}
+
+static struct qda_iommu_device *assign_available_device(struct qda_memory_manager *mem_mgr,
+							struct qda_session *session)
+{
+	struct qda_iommu_device *selected_dev = NULL;
+	int i;
+
+	for (i = 0; i < mem_mgr->max_devices; i++) {
+		struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
+
+		if (!iommu_dev)
+			continue;
+
+		guard(spinlock_irqsave)(&iommu_dev->lock);
+		if (!iommu_dev->assigned_session) {
+			iommu_dev->assigned_session = session;
+			selected_dev = iommu_dev;
+			kref_get(&selected_dev->ref);
+			break;
+		}
+	}
+
+	return selected_dev;
+}
+
+/**
+ * qda_memory_manager_assign_device() - Give a session a context bank
+ * @mem_mgr: Memory manager of the device
+ * @session: Session to assign a context bank to
+ *
+ * Assigns a free context bank to @session, which then owns it exclusively
+ * until the session is released. Does nothing if the session already has
+ * one.
+ *
+ * On success the session owns a reference on the context bank and must
+ * release it with qda_memory_manager_release_device().
+ *
+ * Return: 0 on success, -EBUSY if every context bank is already taken
+ */
+int qda_memory_manager_assign_device(struct qda_memory_manager *mem_mgr,
+				     struct qda_session *session)
+{
+	struct qda_iommu_device *selected_dev;
+
+	guard(mutex)(&mem_mgr->assignment_lock);
+
+	if (session->assigned_iommu_dev)
+		return 0;
+
+	selected_dev = assign_available_device(mem_mgr, session);
+	if (!selected_dev) {
+		drm_dbg_driver(&session->qda_dev->drm_dev, "No context bank available\n");
+		return -EBUSY;
+	}
+
+	session->assigned_iommu_dev = selected_dev;
+
+	return 0;
+}
+
+/**
+ * qda_memory_manager_release_device() - Release a session's context bank
+ * @iommu_dev: Device previously obtained from qda_memory_manager_assign_device()
+ *
+ * Drops the session's reference and frees the context bank up for the next
+ * session.
+ */
+void qda_memory_manager_release_device(struct qda_iommu_device *iommu_dev)
+{
+	scoped_guard(spinlock_irqsave, &iommu_dev->lock)
+		iommu_dev->assigned_session = NULL;
+
+	kref_put(&iommu_dev->ref, iommu_device_free);
+}
+
+/**
+ * qda_memory_manager_register_device() - Add a context bank to the registry
+ * @mem_mgr: Memory manager of the device
+ * @iommu_dev: Context bank to register
+ *
+ * Return: 0 on success, -ENOSPC if the registry is full
+ */
+int qda_memory_manager_register_device(struct qda_memory_manager *mem_mgr,
+				       struct qda_iommu_device *iommu_dev)
+{
+	int i;
+
+	spin_lock_init(&iommu_dev->lock);
+	kref_init(&iommu_dev->ref);
+	iommu_dev->assigned_session = NULL;
+
+	guard(mutex)(&mem_mgr->assignment_lock);
+	for (i = 0; i < mem_mgr->max_devices; i++) {
+		if (!mem_mgr->devices[i]) {
+			mem_mgr->devices[i] = iommu_dev;
+			return 0;
+		}
+	}
+
+	return -ENOSPC;
+}
+
+/**
+ * qda_memory_manager_unregister_device() - Remove a context bank
+ * @mem_mgr: Memory manager of the device
+ * @iommu_dev: Context bank to unregister
+ *
+ * Takes the registry's reference away. The object itself stays alive until
+ * any session still holding it is released.
+ */
+void qda_memory_manager_unregister_device(struct qda_memory_manager *mem_mgr,
+					  struct qda_iommu_device *iommu_dev)
+{
+	int i;
+
+	scoped_guard(mutex, &mem_mgr->assignment_lock) {
+		for (i = 0; i < mem_mgr->max_devices; i++) {
+			if (mem_mgr->devices[i] == iommu_dev) {
+				mem_mgr->devices[i] = NULL;
+				break;
+			}
+		}
+	}
+
+	kref_put(&iommu_dev->ref, iommu_device_free);
+}
+
+/**
+ * qda_memory_manager_init() - Initialise the context bank registry
+ * @mem_mgr: Memory manager to initialise
+ * @max_devices: Number of context banks to make room for
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_memory_manager_init(struct qda_memory_manager *mem_mgr, int max_devices)
+{
+	if (max_devices <= 0)
+		return -EINVAL;
+
+	mem_mgr->devices = kcalloc(max_devices, sizeof(*mem_mgr->devices), GFP_KERNEL);
+	if (!mem_mgr->devices)
+		return -ENOMEM;
+
+	mem_mgr->max_devices = max_devices;
+	mutex_init(&mem_mgr->assignment_lock);
+
+	return 0;
+}
+
+/**
+ * qda_memory_manager_exit() - Tear down the context bank registry
+ * @mem_mgr: Memory manager to tear down
+ *
+ * Context banks are unregistered as their devices are destroyed, so the
+ * registry is expected to be empty by the time this runs.
+ */
+void qda_memory_manager_exit(struct qda_memory_manager *mem_mgr)
+{
+	kfree(mem_mgr->devices);
+	mem_mgr->devices = NULL;
+	mem_mgr->max_devices = 0;
+	mutex_destroy(&mem_mgr->assignment_lock);
+}
diff --git a/drivers/accel/qda/qda_memory_manager.h b/drivers/accel/qda/qda_memory_manager.h
new file mode 100644
index 000000000000..13354158674b
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_manager.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_MEMORY_MANAGER_H__
+#define __QDA_MEMORY_MANAGER_H__
+
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+struct qda_dev;
+struct qda_session;
+
+/**
+ * struct qda_iommu_device - A context bank available for memory allocation
+ *
+ * Wraps one compute context bank device. Each one is exclusively owned by at
+ * most one session at a time, which is what keeps the buffers of one session
+ * out of the address space of another.
+ *
+ * The object outlives its registration: a session keeps a reference through
+ * @ref, so the context bank can be unregistered while a session still holds
+ * it.
+ */
+struct qda_iommu_device {
+	/** @dev: The context bank device to allocate from */
+	struct device *dev;
+	/** @qdev: Back-pointer to the parent QDA device */
+	struct qda_dev *qdev;
+	/** @assigned_session: Session owning this device, or NULL if free */
+	struct qda_session *assigned_session;
+	/** @ref: Reference count controlling when the object is freed */
+	struct kref ref;
+	/** @lock: Protects @assigned_session */
+	spinlock_t lock;
+};
+
+/**
+ * struct qda_memory_manager - Registry of the device's context banks
+ *
+ * Holds every context bank of one QDA device and hands them out to sessions,
+ * one each.
+ */
+struct qda_memory_manager {
+	/** @devices: Array of registered context banks, sized to @max_devices */
+	struct qda_iommu_device **devices;
+	/** @max_devices: Capacity of the @devices array */
+	int max_devices;
+	/** @assignment_lock: Protects registration and assignment */
+	struct mutex assignment_lock;
+};
+
+int qda_memory_manager_init(struct qda_memory_manager *mem_mgr, int max_devices);
+void qda_memory_manager_exit(struct qda_memory_manager *mem_mgr);
+
+int qda_memory_manager_register_device(struct qda_memory_manager *mem_mgr,
+				       struct qda_iommu_device *iommu_dev);
+void qda_memory_manager_unregister_device(struct qda_memory_manager *mem_mgr,
+					  struct qda_iommu_device *iommu_dev);
+
+int qda_memory_manager_assign_device(struct qda_memory_manager *mem_mgr,
+				     struct qda_session *session);
+void qda_memory_manager_release_device(struct qda_iommu_device *iommu_dev);
+
+#endif /* __QDA_MEMORY_MANAGER_H__ */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 78b5f31974c1..2c9d79937b10 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -59,6 +59,18 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
 	drm_dev_unplug(&qdev->drm_dev);
 	qdev->rpdev = NULL;
 	qda_cb_unpopulate(qdev);
+	qda_deinit_device(qdev);
+}
+
+static int qda_count_cb_nodes(struct device_node *parent_node)
+{
+	int count = 0;
+
+	for_each_available_child_of_node_scoped(parent_node, child)
+		if (of_device_is_compatible(child, "qcom,fastrpc-compute-cb"))
+			count++;
+
+	return count;
 }
 
 static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
@@ -80,14 +92,24 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
 		return ret;
 	}
 
-	ret = qda_cb_populate(qdev, rpdev->dev.of_node);
+	ret = qda_init_device(qdev, qda_count_cb_nodes(rpdev->dev.of_node));
 	if (ret)
 		return ret;
 
+	ret = qda_cb_populate(qdev, rpdev->dev.of_node);
+	if (ret)
+		goto err_deinit;
+
 	ret = qda_register_device(qdev);
 	if (ret)
-		qda_cb_unpopulate(qdev);
+		goto err_unpopulate;
 
+	return 0;
+
+err_unpopulate:
+	qda_cb_unpopulate(qdev);
+err_deinit:
+	qda_deinit_device(qdev);
 	return ret;
 }
 

-- 
2.34.1


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

* [PATCH v4 05/25] accel/qda: Add UAPI header and DSP query ioctl
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (3 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 04/25] accel/qda: Assign a context bank to each session Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:21 ` [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP Ekansh Gupta
                   ` (20 subsequent siblings)
  25 siblings, 0 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

A system has one accel node per DSP domain, and which node is which is
not something user space can tell from the node name: /dev/accel/accel0
may be the CDSP on one platform and the ADSP on another. An application
that wants a particular DSP has to open the nodes and ask.

Add DRM_IOCTL_QDA_QUERY, which returns the name of the DSP behind a
given node, taken from the "label" property of its device tree node.
The ioctl takes a query_type selector so that later additions (DSP
capabilities, attributes) do not each need an ioctl of their own, and
is DRM_IOWR rather than DRM_IOR so that the selector reaches the
kernel.

Unknown query types are rejected with -EINVAL rather than returning
zeroes, so that user space can tell an unsupported query on an old
kernel apart from a query that legitimately returned nothing.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 MAINTAINERS                   |  1 +
 drivers/accel/qda/Makefile    |  1 +
 drivers/accel/qda/qda_drv.c   |  8 ++++++++
 drivers/accel/qda/qda_ioctl.c | 30 ++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.h | 13 +++++++++++++
 include/uapi/drm/qda_accel.h  | 41 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 94 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 77687a35ac13..1316e63c1151 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22982,6 +22982,7 @@ S:	Supported
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	Documentation/accel/qda/
 F:	drivers/accel/qda/
+F:	include/uapi/drm/qda_accel.h
 
 QUALCOMM EMAC GIGABIT ETHERNET DRIVER
 M:	Timur Tabi <timur@kernel.org>
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 1ca991e198ed..402e7dbebbf0 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_ioctl.o \
 	qda_memory_manager.o \
 	qda_rpmsg.o \
 	qda_soc.o
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index a99e23933f6b..e722135238f3 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -8,8 +8,10 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
 #include <drm/drm_print.h>
+#include <drm/qda_accel.h>
 
 #include "qda_drv.h"
+#include "qda_ioctl.h"
 
 static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
 {
@@ -54,11 +56,17 @@ static void qda_postclose(struct drm_device *dev, struct drm_file *file)
 
 DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
 
+static const struct drm_ioctl_desc qda_ioctls[] = {
+	DRM_IOCTL_DEF_DRV(QDA_QUERY, qda_ioctl_query, 0),
+};
+
 static const struct drm_driver qda_drm_driver = {
 	.driver_features = DRIVER_COMPUTE_ACCEL,
 	.fops = &qda_accel_fops,
 	.open = qda_open,
 	.postclose = qda_postclose,
+	.ioctls = qda_ioctls,
+	.num_ioctls = ARRAY_SIZE(qda_ioctls),
 	.name = QDA_DRIVER_NAME,
 	.desc = "Qualcomm DSP Accelerator Driver",
 };
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
new file mode 100644
index 000000000000..380c2526d9d2
--- /dev/null
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/string.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/qda_accel.h>
+#include "qda_drv.h"
+#include "qda_ioctl.h"
+
+/**
+ * qda_ioctl_query() - Query a property of the DSP
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_query)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(dev);
+	struct drm_qda_query *args = data;
+
+	switch (args->query_type) {
+	case QDA_QUERY_DSP_NAME:
+		strscpy(args->dsp_name, qdev->dsp_name, sizeof(args->dsp_name));
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
new file mode 100644
index 000000000000..b8fd536a111f
--- /dev/null
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_IOCTL_H__
+#define __QDA_IOCTL_H__
+
+#include "qda_drv.h"
+
+int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_priv);
+
+#endif /* __QDA_IOCTL_H__ */
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
new file mode 100644
index 000000000000..b9dc4787bd42
--- /dev/null
+++ b/include/uapi/drm/qda_accel.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_ACCEL_H__
+#define __QDA_ACCEL_H__
+
+#include "drm.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define DRM_QDA_QUERY		0x00
+
+#define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
+					 struct drm_qda_query)
+
+/* Query type definitions for drm_qda_query */
+#define QDA_QUERY_DSP_NAME		1
+
+/**
+ * struct drm_qda_query - Device information query structure
+ * @query_type: Type of query (input)
+ * @dsp_name: Null-terminated name of the DSP (returned when query_type is
+ *            QDA_QUERY_DSP_NAME)
+ *
+ * This structure is used with DRM_IOCTL_QDA_QUERY to query device attributes
+ * based on @query_type.
+ */
+struct drm_qda_query {
+	__u32 query_type;
+	__u8 dsp_name[16];
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __QDA_ACCEL_H__ */

-- 
2.34.1


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

* [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (4 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 05/25] accel/qda: Add UAPI header and DSP query ioctl Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:51   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 07/25] accel/qda: Add DMA-BUF import Ekansh Gupta
                   ` (19 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Offloading work to the DSP means handing it buffers, and the DSP can
only reach memory mapped into one of its context banks. Allocate those
buffers as GEM objects so that user space gets the usual handle-based
lifetime and the usual mmap path, and so that buffers can later be
shared with other drivers through PRIME.

Buffers are allocated with dma_alloc_coherent() from the context bank
the session owns, which is assigned on the session's first allocation.
Coherent memory avoids explicit cache maintenance on every invocation,
at the cost of uncached CPU access.

The DSP does not take a context bank identifier alongside an address.
It derives the context bank from the upper bits of the address itself,
so the stream ID of the bank is folded into every address handed to it,
at a bit position that differs between SoCs. The DMA API must never see
those bits, so they are stripped again before each free and mmap. This
is also why the context bank devices are given a DMA mask covering only
the lower bits: the allocator must not hand back an address that would
overlap the stream ID field.

Add DRM_IOCTL_QDA_GEM_CREATE to allocate a buffer and
DRM_IOCTL_QDA_GEM_MMAP_OFFSET to find the offset to mmap() it at.
Nothing passes these buffers to the DSP yet; that comes with the
invocation path.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Makefile             |   2 +
 drivers/accel/qda/qda_cb.c             |  14 ++++-
 drivers/accel/qda/qda_drv.c            |   4 +-
 drivers/accel/qda/qda_gem.c            | 108 +++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_gem.h            |  44 ++++++++++++++
 drivers/accel/qda/qda_ioctl.c          |  45 ++++++++++++++
 drivers/accel/qda/qda_ioctl.h          |   2 +
 drivers/accel/qda/qda_memory_dma.c     |  72 ++++++++++++++++++++++
 drivers/accel/qda/qda_memory_dma.h     |  20 ++++++
 drivers/accel/qda/qda_memory_manager.c |  37 +++++++++++
 drivers/accel/qda/qda_memory_manager.h |   8 +++
 drivers/accel/qda/qda_soc.c            |   2 +
 drivers/accel/qda/qda_soc.h            |  28 +++++++++
 include/uapi/drm/qda_accel.h           |  38 +++++++++++-
 14 files changed, 420 insertions(+), 4 deletions(-)

diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 402e7dbebbf0..2d3bcc3050eb 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -8,7 +8,9 @@ obj-$(CONFIG_DRM_ACCEL_QDA)	:= qda.o
 qda-y := \
 	qda_cb.o \
 	qda_drv.o \
+	qda_gem.o \
 	qda_ioctl.o \
+	qda_memory_dma.o \
 	qda_memory_manager.o \
 	qda_rpmsg.o \
 	qda_soc.o
diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
index 6f1cff1133f9..033d1502282a 100644
--- a/drivers/accel/qda/qda_cb.c
+++ b/drivers/accel/qda/qda_cb.c
@@ -10,7 +10,7 @@
 #include "qda_memory_manager.h"
 #include "qda_soc.h"
 
-static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev)
+static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev, u32 sid)
 {
 	struct qda_iommu_device *iommu_dev;
 	int ret;
@@ -21,6 +21,7 @@ static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev)
 
 	iommu_dev->dev = cb_dev;
 	iommu_dev->qdev = qdev;
+	iommu_dev->sid = sid;
 
 	ret = qda_memory_manager_register_device(qdev->iommu_mgr, iommu_dev);
 	if (ret) {
@@ -50,8 +51,17 @@ static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_nod
 	struct qda_cb_dev *entry;
 	struct platform_device *pdev;
 	u32 dma_bits;
+	u32 sid = 0;
 	int ret;
 
+	/*
+	 * The stream ID identifies the context bank to the DSP. A node without
+	 * one is accepted so that buffers can still be allocated from it, but
+	 * the DSP will then see every such bank as stream ID 0.
+	 */
+	if (of_property_read_u32(cb_node, "reg", &sid))
+		drm_info(&qdev->drm_dev, "No stream ID in %pOFn, using 0\n", cb_node);
+
 	pdev = of_platform_device_create(cb_node, NULL, qdev->dev);
 	if (!pdev) {
 		drm_err(&qdev->drm_dev, "Failed to create CB device for %pOFn\n", cb_node);
@@ -66,7 +76,7 @@ static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_nod
 		goto err_destroy;
 	}
 
-	ret = qda_cb_setup_device(qdev, &pdev->dev);
+	ret = qda_cb_setup_device(qdev, &pdev->dev, sid);
 	if (ret)
 		goto err_destroy;
 
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index e722135238f3..37019ff38ecb 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -58,10 +58,12 @@ DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
 
 static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_QUERY, qda_ioctl_query, 0),
+	DRM_IOCTL_DEF_DRV(QDA_GEM_CREATE, qda_ioctl_gem_create, 0),
+	DRM_IOCTL_DEF_DRV(QDA_GEM_MMAP_OFFSET, qda_ioctl_gem_mmap_offset, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
-	.driver_features = DRIVER_COMPUTE_ACCEL,
+	.driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
 	.fops = &qda_accel_fops,
 	.open = qda_open,
 	.postclose = qda_postclose,
diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
new file mode 100644
index 000000000000..864bac914e48
--- /dev/null
+++ b/drivers/accel/qda/qda_gem.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <drm/drm_gem.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_dma.h"
+#include "qda_memory_manager.h"
+
+static void qda_gem_free_object(struct drm_gem_object *gem_obj)
+{
+	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
+
+	if (qda_gem_obj->virt)
+		qda_memory_manager_free(qda_gem_obj);
+
+	drm_gem_object_release(gem_obj);
+	kfree(qda_gem_obj);
+}
+
+static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struct *vma)
+{
+	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
+	int ret;
+
+	/* The fake offset only identifies the object, it does not index into it */
+	vma->vm_pgoff = 0;
+
+	ret = qda_dma_mmap(qda_gem_obj, vma);
+	if (ret)
+		return ret;
+
+	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
+
+	return 0;
+}
+
+static const struct drm_gem_object_funcs qda_gem_object_funcs = {
+	.free = qda_gem_free_object,
+	.mmap = qda_gem_mmap_obj,
+};
+
+/**
+ * qda_gem_create_object() - Allocate a buffer the DSP can access
+ * @drm_dev: DRM device
+ * @iommu_mgr: Memory manager to allocate from
+ * @size: Requested size in bytes
+ * @session: Session the buffer belongs to
+ *
+ * Return: The new GEM object on success, ERR_PTR on failure
+ */
+struct drm_gem_object *qda_gem_create_object(struct drm_device *drm_dev,
+					     struct qda_memory_manager *iommu_mgr,
+					     size_t size, struct qda_session *session)
+{
+	struct qda_gem_obj *qda_gem_obj;
+	size_t aligned_size;
+	int ret;
+
+	aligned_size = PAGE_ALIGN(size);
+	if (!aligned_size)
+		return ERR_PTR(-EINVAL);
+
+	qda_gem_obj = kzalloc_obj(*qda_gem_obj);
+	if (!qda_gem_obj)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_gem_object_init(drm_dev, &qda_gem_obj->base, aligned_size);
+	if (ret) {
+		kfree(qda_gem_obj);
+		return ERR_PTR(ret);
+	}
+
+	qda_gem_obj->base.funcs = &qda_gem_object_funcs;
+	qda_gem_obj->size = aligned_size;
+
+	ret = qda_memory_manager_alloc(iommu_mgr, qda_gem_obj, session);
+	if (ret) {
+		drm_gem_object_release(&qda_gem_obj->base);
+		kfree(qda_gem_obj);
+		return ERR_PTR(ret);
+	}
+
+	return &qda_gem_obj->base;
+}
+
+/**
+ * qda_gem_create_handle() - Give user space a handle to a GEM object
+ * @file_priv: DRM file to install the handle in
+ * @gem_obj: Object to install
+ * @handle: Returned handle
+ *
+ * Consumes the caller's reference on @gem_obj: the handle holds one of its
+ * own, so on success the object stays alive until user space closes it, and
+ * on failure it is destroyed here.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_gem_create_handle(struct drm_file *file_priv, struct drm_gem_object *gem_obj, u32 *handle)
+{
+	int ret;
+
+	ret = drm_gem_handle_create(file_priv, gem_obj, handle);
+	drm_gem_object_put(gem_obj);
+
+	return ret;
+}
diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
new file mode 100644
index 000000000000..af3b9dfc7509
--- /dev/null
+++ b/drivers/accel/qda/qda_gem.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#ifndef __QDA_GEM_H__
+#define __QDA_GEM_H__
+
+#include <linux/dma-mapping.h>
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
+
+struct qda_iommu_device;
+struct qda_memory_manager;
+struct qda_session;
+
+/**
+ * struct qda_gem_obj - A buffer the DSP can access
+ */
+struct qda_gem_obj {
+	/** @base: DRM GEM object base; must be first member */
+	struct drm_gem_object base;
+	/** @iommu_dev: Context bank this buffer is mapped into */
+	struct qda_iommu_device *iommu_dev;
+	/** @virt: Kernel virtual address of the buffer */
+	void *virt;
+	/** @dma_addr: Address of the buffer as the DSP sees it */
+	dma_addr_t dma_addr;
+	/** @size: Size of the buffer in bytes */
+	size_t size;
+};
+
+/**
+ * to_qda_gem_obj - Cast a drm_gem_object pointer to qda_gem_obj
+ * @gem_obj: Pointer to the embedded drm_gem_object
+ */
+#define to_qda_gem_obj(gem_obj) container_of(gem_obj, struct qda_gem_obj, base)
+
+struct drm_gem_object *qda_gem_create_object(struct drm_device *drm_dev,
+					     struct qda_memory_manager *iommu_mgr,
+					     size_t size, struct qda_session *session);
+int qda_gem_create_handle(struct drm_file *file_priv, struct drm_gem_object *gem_obj, u32 *handle);
+
+#endif /* __QDA_GEM_H__ */
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 380c2526d9d2..b11743ab791e 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -3,8 +3,10 @@
 #include <linux/string.h>
 #include <drm/drm_device.h>
 #include <drm/drm_file.h>
+#include <drm/drm_gem.h>
 #include <drm/qda_accel.h>
 #include "qda_drv.h"
+#include "qda_gem.h"
 #include "qda_ioctl.h"
 
 /**
@@ -28,3 +30,46 @@ int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_pr
 		return -EINVAL;
 	}
 }
+
+/**
+ * qda_ioctl_gem_create() - Allocate a buffer the DSP can access
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_gem_create)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(dev);
+	struct drm_qda_gem_create *args = data;
+	struct drm_gem_object *gem_obj;
+
+	if (args->pad)
+		return -EINVAL;
+
+	gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr, args->size,
+					file_priv->driver_priv);
+	if (IS_ERR(gem_obj))
+		return PTR_ERR(gem_obj);
+
+	return qda_gem_create_handle(file_priv, gem_obj, &args->handle);
+}
+
+/**
+ * qda_ioctl_gem_mmap_offset() - Get the mmap offset of a buffer
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_gem_mmap_offset)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_gem_mmap_offset *args = data;
+
+	if (args->pad)
+		return -EINVAL;
+
+	return drm_gem_dumb_map_offset(file_priv, dev, args->handle, &args->offset);
+}
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index b8fd536a111f..d1cbbfb6d965 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -9,5 +9,7 @@
 #include "qda_drv.h"
 
 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);
 
 #endif /* __QDA_IOCTL_H__ */
diff --git a/drivers/accel/qda/qda_memory_dma.c b/drivers/accel/qda/qda_memory_dma.c
new file mode 100644
index 000000000000..0f4715a55b9d
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_dma.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-mapping.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_dma.h"
+#include "qda_memory_manager.h"
+#include "qda_soc.h"
+
+/*
+ * The DSP identifies the context bank a buffer belongs to from the upper bits
+ * of its address, so the stream ID is folded into every address handed to it.
+ * The DMA API only ever sees the plain address, which is why the context bank
+ * devices are created with a mask that covers just those lower bits.
+ */
+static dma_addr_t qda_gem_dma_addr(struct qda_gem_obj *gem_obj)
+{
+	return qda_soc_dma_addr(gem_obj->iommu_dev->qdev->soc_data, gem_obj->dma_addr);
+}
+
+/**
+ * qda_dma_alloc() - Allocate a buffer from a context bank
+ * @iommu_dev: Context bank to allocate from
+ * @gem_obj: GEM object to back with the allocation
+ * @size: Size to allocate in bytes
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_dma_alloc(struct qda_iommu_device *iommu_dev,
+		  struct qda_gem_obj *gem_obj, size_t size)
+{
+	dma_addr_t dma_addr;
+	void *virt;
+
+	virt = dma_alloc_coherent(iommu_dev->dev, size, &dma_addr, GFP_KERNEL);
+	if (!virt)
+		return -ENOMEM;
+
+	gem_obj->virt = virt;
+	gem_obj->dma_addr = dma_addr +
+			    qda_soc_sid_offset(iommu_dev->qdev->soc_data, iommu_dev->sid);
+	gem_obj->iommu_dev = iommu_dev;
+
+	return 0;
+}
+
+/**
+ * qda_dma_free() - Release a buffer back to its context bank
+ * @gem_obj: GEM object to free
+ */
+void qda_dma_free(struct qda_gem_obj *gem_obj)
+{
+	dma_free_coherent(gem_obj->iommu_dev->dev, gem_obj->size,
+			  gem_obj->virt, qda_gem_dma_addr(gem_obj));
+
+	gem_obj->virt = NULL;
+	gem_obj->dma_addr = 0;
+	gem_obj->iommu_dev = NULL;
+}
+
+/**
+ * qda_dma_mmap() - Map a buffer into user space
+ * @gem_obj: GEM object to map
+ * @vma: Virtual memory area to map into
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_dma_mmap(struct qda_gem_obj *gem_obj, struct vm_area_struct *vma)
+{
+	return dma_mmap_coherent(gem_obj->iommu_dev->dev, vma, gem_obj->virt,
+				 qda_gem_dma_addr(gem_obj), gem_obj->size);
+}
diff --git a/drivers/accel/qda/qda_memory_dma.h b/drivers/accel/qda/qda_memory_dma.h
new file mode 100644
index 000000000000..f4d76b806df6
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_dma.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_MEMORY_DMA_H__
+#define __QDA_MEMORY_DMA_H__
+
+#include <linux/mm_types.h>
+#include <linux/types.h>
+
+struct qda_gem_obj;
+struct qda_iommu_device;
+
+int qda_dma_alloc(struct qda_iommu_device *iommu_dev,
+		  struct qda_gem_obj *gem_obj, size_t size);
+void qda_dma_free(struct qda_gem_obj *gem_obj);
+int qda_dma_mmap(struct qda_gem_obj *gem_obj, struct vm_area_struct *vma);
+
+#endif /* __QDA_MEMORY_DMA_H__ */
diff --git a/drivers/accel/qda/qda_memory_manager.c b/drivers/accel/qda/qda_memory_manager.c
index 87e8d3485e81..ccab85c17203 100644
--- a/drivers/accel/qda/qda_memory_manager.c
+++ b/drivers/accel/qda/qda_memory_manager.c
@@ -5,6 +5,8 @@
 #include <linux/spinlock.h>
 #include <drm/drm_print.h>
 #include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_dma.h"
 #include "qda_memory_manager.h"
 
 static void iommu_device_free(struct kref *ref)
@@ -86,6 +88,41 @@ void qda_memory_manager_release_device(struct qda_iommu_device *iommu_dev)
 	kref_put(&iommu_dev->ref, iommu_device_free);
 }
 
+/**
+ * qda_memory_manager_alloc() - Allocate a buffer for a session
+ * @mem_mgr: Memory manager of the device
+ * @gem_obj: GEM object to back with the allocation
+ * @session: Session the buffer belongs to
+ *
+ * Allocates from the context bank the session owns, assigning it one on its
+ * first allocation.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_memory_manager_alloc(struct qda_memory_manager *mem_mgr, struct qda_gem_obj *gem_obj,
+			     struct qda_session *session)
+{
+	int ret;
+
+	ret = qda_memory_manager_assign_device(mem_mgr, session);
+	if (ret)
+		return ret;
+
+	return qda_dma_alloc(session->assigned_iommu_dev, gem_obj, gem_obj->size);
+}
+
+/**
+ * qda_memory_manager_free() - Release a buffer
+ * @gem_obj: GEM object to free
+ *
+ * The buffer remembers the context bank it was allocated from, so no
+ * memory manager is needed here.
+ */
+void qda_memory_manager_free(struct qda_gem_obj *gem_obj)
+{
+	qda_dma_free(gem_obj);
+}
+
 /**
  * qda_memory_manager_register_device() - Add a context bank to the registry
  * @mem_mgr: Memory manager of the device
diff --git a/drivers/accel/qda/qda_memory_manager.h b/drivers/accel/qda/qda_memory_manager.h
index 13354158674b..d2ca6682d58b 100644
--- a/drivers/accel/qda/qda_memory_manager.h
+++ b/drivers/accel/qda/qda_memory_manager.h
@@ -13,6 +13,7 @@
 #include <linux/types.h>
 
 struct qda_dev;
+struct qda_gem_obj;
 struct qda_session;
 
 /**
@@ -31,6 +32,8 @@ struct qda_iommu_device {
 	struct device *dev;
 	/** @qdev: Back-pointer to the parent QDA device */
 	struct qda_dev *qdev;
+	/** @sid: Stream ID the DSP identifies this context bank by */
+	u32 sid;
 	/** @assigned_session: Session owning this device, or NULL if free */
 	struct qda_session *assigned_session;
 	/** @ref: Reference count controlling when the object is freed */
@@ -66,4 +69,9 @@ int qda_memory_manager_assign_device(struct qda_memory_manager *mem_mgr,
 				     struct qda_session *session);
 void qda_memory_manager_release_device(struct qda_iommu_device *iommu_dev);
 
+int qda_memory_manager_alloc(struct qda_memory_manager *mem_mgr,
+			     struct qda_gem_obj *gem_obj,
+			     struct qda_session *session);
+void qda_memory_manager_free(struct qda_gem_obj *gem_obj);
+
 #endif /* __QDA_MEMORY_MANAGER_H__ */
diff --git a/drivers/accel/qda/qda_soc.c b/drivers/accel/qda/qda_soc.c
index 323b620b8adb..fb316b74103d 100644
--- a/drivers/accel/qda/qda_soc.c
+++ b/drivers/accel/qda/qda_soc.c
@@ -5,11 +5,13 @@
 #include "qda_soc.h"
 
 const struct qda_soc_data kaanapali_soc_data = {
+	.sid_pos = 56,
 	.dma_addr_bits_cdsp = 34,
 	.dma_addr_bits_default = 32,
 };
 
 const struct qda_soc_data default_soc_data = {
+	.sid_pos = 32,
 	.dma_addr_bits_cdsp = 32,
 	.dma_addr_bits_default = 32,
 };
diff --git a/drivers/accel/qda/qda_soc.h b/drivers/accel/qda/qda_soc.h
index b052964f11ae..fb5b786d0928 100644
--- a/drivers/accel/qda/qda_soc.h
+++ b/drivers/accel/qda/qda_soc.h
@@ -6,6 +6,8 @@
 #ifndef __QDA_SOC_H__
 #define __QDA_SOC_H__
 
+#include <linux/bits.h>
+#include <linux/dma-mapping.h>
 #include <linux/types.h>
 
 struct qda_dev;
@@ -16,6 +18,8 @@ struct qda_dev;
  * Selected by OF match on the fastrpc node; see qda_rpmsg.c.
  */
 struct qda_soc_data {
+	/** @sid_pos: Bit position of the stream ID in DSP-visible addresses */
+	u32 sid_pos;
 	/** @dma_addr_bits_cdsp: DMA address width of CDSP context banks */
 	u32 dma_addr_bits_cdsp;
 	/** @dma_addr_bits_default: DMA address width of all other context banks */
@@ -27,4 +31,28 @@ extern const struct qda_soc_data default_soc_data;
 
 u32 qda_soc_dma_addr_bits(const struct qda_dev *qdev);
 
+/**
+ * qda_soc_sid_offset() - Stream ID bits to fold into a DSP-visible address
+ * @soc_data: SoC parameters of the device
+ * @sid: Stream ID of the context bank
+ *
+ * Return: @sid shifted to its SoC-specific position
+ */
+static inline u64 qda_soc_sid_offset(const struct qda_soc_data *soc_data, u32 sid)
+{
+	return (u64)sid << soc_data->sid_pos;
+}
+
+/**
+ * qda_soc_dma_addr() - Recover the DMA API address from a DSP-visible address
+ * @soc_data: SoC parameters of the device
+ * @iova: Address with the stream ID folded in
+ *
+ * Return: @iova with the stream ID bits cleared
+ */
+static inline dma_addr_t qda_soc_dma_addr(const struct qda_soc_data *soc_data, u64 iova)
+{
+	return iova & GENMASK_ULL(soc_data->sid_pos - 1, 0);
+}
+
 #endif /* __QDA_SOC_H__ */
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index b9dc4787bd42..8e64480cdd82 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -12,10 +12,16 @@
 extern "C" {
 #endif
 
-#define DRM_QDA_QUERY		0x00
+#define DRM_QDA_QUERY			0x00
+#define DRM_QDA_GEM_CREATE		0x01
+#define DRM_QDA_GEM_MMAP_OFFSET		0x02
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
+#define DRM_IOCTL_QDA_GEM_CREATE	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_GEM_CREATE, \
+					 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)
 
 /* Query type definitions for drm_qda_query */
 #define QDA_QUERY_DSP_NAME		1
@@ -34,6 +40,36 @@ struct drm_qda_query {
 	__u8 dsp_name[16];
 };
 
+/**
+ * struct drm_qda_gem_create - Buffer allocation parameters
+ * @size: Size of the buffer to allocate in bytes (input)
+ * @handle: Handle of the new buffer (output)
+ * @pad: Must be zero
+ *
+ * Allocates a buffer the DSP can access and returns a handle to it. The
+ * handle identifies the buffer in later ioctls, and the buffer is released
+ * when the last handle to it is closed.
+ */
+struct drm_qda_gem_create {
+	__u64 size;
+	__u32 handle;
+	__u32 pad;
+};
+
+/**
+ * struct drm_qda_gem_mmap_offset - Buffer mmap offset query
+ * @offset: Offset to pass to mmap() (output)
+ * @handle: Handle of the buffer (input)
+ * @pad: Must be zero
+ *
+ * Returns the offset at which a buffer can be mmap()ed on the accel node.
+ */
+struct drm_qda_gem_mmap_offset {
+	__u64 offset;
+	__u32 handle;
+	__u32 pad;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 07/25] accel/qda: Add DMA-BUF import
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (5 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:51   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device Ekansh Gupta
                   ` (18 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Buffers the DSP works on often originate elsewhere - a camera frame to
run inference on, a video surface to post-process. Importing those
directly avoids a copy, so wire up the DRM PRIME import path.

A buffer has to be mapped into the context bank of the session that
imports it, not into the parent device's address space, or the DSP
would not be able to reach it under the session's stream ID. The core
PRIME helper takes the device to attach to, so pass the session's
context bank. It does not pass the calling file down to the import
callback, so the session is published on the device for the duration
of the call.

The DSP is told where a buffer is by a single address, so only buffers
that map as one contiguous range can be described to it. Whether an
exporter or an IOMMU produces a single range is not guaranteed, so
check rather than assume: handing over a multi-range buffer as its
first range alone would leave the DSP reading and writing past the end
of it into whatever follows in the context bank. Contiguous imports,
such as from the CMA DMA-BUF heap, are accepted; the rest are rejected.

Imported buffers own no memory, they only hold a mapping of the
exporter's, so they are released by handing the mapping back rather
than freeing anything, and cannot be mmap()ed through this driver.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Makefile         |   1 +
 drivers/accel/qda/qda_drv.c        |   6 ++
 drivers/accel/qda/qda_drv.h        |  10 ++
 drivers/accel/qda/qda_gem.c        |  42 ++++++++-
 drivers/accel/qda/qda_gem.h        |  11 ++-
 drivers/accel/qda/qda_memory_dma.c |  13 +++
 drivers/accel/qda/qda_memory_dma.h |   2 +
 drivers/accel/qda/qda_prime.c      | 184 +++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_prime.h      |  25 +++++
 9 files changed, 291 insertions(+), 3 deletions(-)

diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 2d3bcc3050eb..01c0319da900 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -12,5 +12,6 @@ qda-y := \
 	qda_ioctl.o \
 	qda_memory_dma.o \
 	qda_memory_manager.o \
+	qda_prime.o \
 	qda_rpmsg.o \
 	qda_soc.o
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 37019ff38ecb..5e953d1f98bf 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -12,6 +12,7 @@
 
 #include "qda_drv.h"
 #include "qda_ioctl.h"
+#include "qda_prime.h"
 
 static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
 {
@@ -69,6 +70,9 @@ static const struct drm_driver qda_drm_driver = {
 	.postclose = qda_postclose,
 	.ioctls = qda_ioctls,
 	.num_ioctls = ARRAY_SIZE(qda_ioctls),
+	.gem_prime_import = qda_gem_prime_import,
+	.gem_prime_import_sg_table = qda_gem_prime_import_sg_table,
+	.prime_fd_to_handle = qda_prime_fd_to_handle,
 	.name = QDA_DRIVER_NAME,
 	.desc = "Qualcomm DSP Accelerator Driver",
 };
@@ -82,6 +86,7 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 		return ERR_CAST(qdev);
 
 	INIT_LIST_HEAD(&qdev->cb_devs);
+	mutex_init(&qdev->import_lock);
 
 	return qdev;
 }
@@ -104,6 +109,7 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
 void qda_deinit_device(struct qda_dev *qdev)
 {
 	qda_memory_manager_exit(qdev->iommu_mgr);
+	mutex_destroy(&qdev->import_lock);
 }
 
 int qda_register_device(struct qda_dev *qdev)
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index c852d3b05310..31aad9057e28 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -8,6 +8,7 @@
 
 #include <linux/device.h>
 #include <linux/list.h>
+#include <linux/mutex.h>
 #include <linux/rpmsg.h>
 #include <linux/types.h>
 #include <drm/drm_device.h>
@@ -50,6 +51,15 @@ struct qda_dev {
 	struct list_head cb_devs;
 	/** @iommu_mgr: Registry of this device's context banks */
 	struct qda_memory_manager *iommu_mgr;
+	/** @import_lock: Serialises PRIME imports, protects @importing_session */
+	struct mutex import_lock;
+	/**
+	 * @importing_session: Session currently importing a buffer. Set only
+	 * while @import_lock is held, to pass the session down to the
+	 * .gem_prime_import callback, which the DRM core does not hand the
+	 * calling file to.
+	 */
+	struct qda_session *importing_session;
 	/** @soc_data: Per-SoC DSP integration parameters */
 	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
index 864bac914e48..a9ab8ccb7893 100644
--- a/drivers/accel/qda/qda_gem.c
+++ b/drivers/accel/qda/qda_gem.c
@@ -3,6 +3,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/slab.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
 #include "qda_drv.h"
 #include "qda_gem.h"
 #include "qda_memory_dma.h"
@@ -12,7 +13,13 @@ static void qda_gem_free_object(struct drm_gem_object *gem_obj)
 {
 	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
 
-	if (qda_gem_obj->virt)
+	/*
+	 * An imported buffer only holds a mapping of memory the exporter owns,
+	 * so hand the mapping back rather than freeing anything.
+	 */
+	if (drm_gem_is_imported(gem_obj))
+		drm_prime_gem_destroy(gem_obj, qda_gem_obj->sgt);
+	else if (qda_gem_obj->virt)
 		qda_memory_manager_free(qda_gem_obj);
 
 	drm_gem_object_release(gem_obj);
@@ -24,6 +31,10 @@ static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struc
 	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
 	int ret;
 
+	/* Imported buffers have to be mapped through the exporter */
+	if (drm_gem_is_imported(gem_obj))
+		return -EINVAL;
+
 	/* The fake offset only identifies the object, it does not index into it */
 	vma->vm_pgoff = 0;
 
@@ -36,8 +47,35 @@ static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struc
 	return 0;
 }
 
-static const struct drm_gem_object_funcs qda_gem_object_funcs = {
+/*
+ * Lets another context bank map this buffer when a session imports the
+ * dma-buf this object was exported as: see qda_gem_prime_import().
+ */
+static struct sg_table *qda_gem_get_sg_table(struct drm_gem_object *gem_obj)
+{
+	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
+	struct sg_table *sgt;
+	int ret;
+
+	if (drm_gem_is_imported(gem_obj))
+		return ERR_PTR(-EOPNOTSUPP);
+
+	sgt = kzalloc_obj(*sgt);
+	if (!sgt)
+		return ERR_PTR(-ENOMEM);
+
+	ret = qda_dma_get_sgtable(qda_gem_obj, sgt);
+	if (ret) {
+		kfree(sgt);
+		return ERR_PTR(ret);
+	}
+
+	return sgt;
+}
+
+const struct drm_gem_object_funcs qda_gem_object_funcs = {
 	.free = qda_gem_free_object,
+	.get_sg_table = qda_gem_get_sg_table,
 	.mmap = qda_gem_mmap_obj,
 };
 
diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
index af3b9dfc7509..1dab494197aa 100644
--- a/drivers/accel/qda/qda_gem.h
+++ b/drivers/accel/qda/qda_gem.h
@@ -6,6 +6,7 @@
 #define __QDA_GEM_H__
 
 #include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
 #include <drm/drm_device.h>
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
@@ -16,14 +17,20 @@ struct qda_session;
 
 /**
  * struct qda_gem_obj - A buffer the DSP can access
+ *
+ * Either allocated by this driver or imported from another one. Use
+ * drm_gem_is_imported() on @base to tell which; imported buffers have no
+ * @virt and own no memory, they only hold a mapping of someone else's.
  */
 struct qda_gem_obj {
 	/** @base: DRM GEM object base; must be first member */
 	struct drm_gem_object base;
 	/** @iommu_dev: Context bank this buffer is mapped into */
 	struct qda_iommu_device *iommu_dev;
-	/** @virt: Kernel virtual address of the buffer */
+	/** @virt: Kernel virtual address, NULL for imported buffers */
 	void *virt;
+	/** @sgt: Mapping of an imported buffer, NULL otherwise */
+	struct sg_table *sgt;
 	/** @dma_addr: Address of the buffer as the DSP sees it */
 	dma_addr_t dma_addr;
 	/** @size: Size of the buffer in bytes */
@@ -36,6 +43,8 @@ struct qda_gem_obj {
  */
 #define to_qda_gem_obj(gem_obj) container_of(gem_obj, struct qda_gem_obj, base)
 
+extern const struct drm_gem_object_funcs qda_gem_object_funcs;
+
 struct drm_gem_object *qda_gem_create_object(struct drm_device *drm_dev,
 					     struct qda_memory_manager *iommu_mgr,
 					     size_t size, struct qda_session *session);
diff --git a/drivers/accel/qda/qda_memory_dma.c b/drivers/accel/qda/qda_memory_dma.c
index 0f4715a55b9d..e7c5d540c0c1 100644
--- a/drivers/accel/qda/qda_memory_dma.c
+++ b/drivers/accel/qda/qda_memory_dma.c
@@ -70,3 +70,16 @@ int qda_dma_mmap(struct qda_gem_obj *gem_obj, struct vm_area_struct *vma)
 	return dma_mmap_coherent(gem_obj->iommu_dev->dev, vma, gem_obj->virt,
 				 qda_gem_dma_addr(gem_obj), gem_obj->size);
 }
+
+/**
+ * qda_dma_get_sgtable() - Describe a buffer's pages for an importer
+ * @gem_obj: GEM object to describe
+ * @sgt: Table to fill in; the caller frees it with sg_free_table()
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_dma_get_sgtable(struct qda_gem_obj *gem_obj, struct sg_table *sgt)
+{
+	return dma_get_sgtable(gem_obj->iommu_dev->dev, sgt, gem_obj->virt,
+			       qda_gem_dma_addr(gem_obj), gem_obj->size);
+}
diff --git a/drivers/accel/qda/qda_memory_dma.h b/drivers/accel/qda/qda_memory_dma.h
index f4d76b806df6..f1688d7a57ea 100644
--- a/drivers/accel/qda/qda_memory_dma.h
+++ b/drivers/accel/qda/qda_memory_dma.h
@@ -9,6 +9,7 @@
 #include <linux/mm_types.h>
 #include <linux/types.h>
 
+struct sg_table;
 struct qda_gem_obj;
 struct qda_iommu_device;
 
@@ -16,5 +17,6 @@ int qda_dma_alloc(struct qda_iommu_device *iommu_dev,
 		  struct qda_gem_obj *gem_obj, size_t size);
 void qda_dma_free(struct qda_gem_obj *gem_obj);
 int qda_dma_mmap(struct qda_gem_obj *gem_obj, struct vm_area_struct *vma);
+int qda_dma_get_sgtable(struct qda_gem_obj *gem_obj, struct sg_table *sgt);
 
 #endif /* __QDA_MEMORY_DMA_H__ */
diff --git a/drivers/accel/qda/qda_prime.c b/drivers/accel/qda/qda_prime.c
new file mode 100644
index 000000000000..7a709e3000d8
--- /dev/null
+++ b/drivers/accel/qda/qda_prime.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-buf.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_print.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_manager.h"
+#include "qda_prime.h"
+#include "qda_soc.h"
+
+/**
+ * qda_gem_prime_import_sg_table() - Wrap an imported buffer in a GEM object
+ * @drm_dev: DRM device
+ * @attach: Attachment the DRM core made to the exporter
+ * @sgt: Mapping of the buffer in the context bank's address space
+ *
+ * Called by the DRM PRIME helpers once they have attached to the exporter
+ * and mapped the buffer into the context bank this session owns.
+ *
+ * The DSP is told where a buffer is by a single address, so only buffers
+ * that map as one contiguous range can be described to it. Whether an
+ * exporter or an IOMMU produces a single range is not guaranteed, so check
+ * rather than assume: a buffer mapped as several ranges would otherwise be
+ * handed to the DSP as its first range alone, leaving the DSP to read and
+ * write past the end of it into whatever happens to follow in the context
+ * bank.
+ *
+ * Return: The new GEM object on success, ERR_PTR on failure
+ */
+struct drm_gem_object *qda_gem_prime_import_sg_table(struct drm_device *drm_dev,
+						     struct dma_buf_attachment *attach,
+						     struct sg_table *sgt)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
+	size_t size = PAGE_ALIGN(attach->dmabuf->size);
+	struct qda_iommu_device *iommu_dev;
+	struct qda_gem_obj *qda_gem_obj;
+	int ret;
+
+	if (drm_prime_get_contiguous_size(sgt) < size) {
+		drm_dbg_driver(drm_dev, "Rejecting non-contiguous buffer of %zu bytes\n", size);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/*
+	 * The buffer was mapped into the context bank of the session doing the
+	 * import, so that is the bank the DSP has to be told about.
+	 */
+	iommu_dev = dev_get_drvdata(attach->dev);
+	if (!iommu_dev)
+		return ERR_PTR(-ENODEV);
+
+	qda_gem_obj = kzalloc_obj(*qda_gem_obj);
+	if (!qda_gem_obj)
+		return ERR_PTR(-ENOMEM);
+
+	ret = drm_gem_object_init(drm_dev, &qda_gem_obj->base, size);
+	if (ret) {
+		kfree(qda_gem_obj);
+		return ERR_PTR(ret);
+	}
+
+	qda_gem_obj->base.funcs = &qda_gem_object_funcs;
+	qda_gem_obj->size = size;
+	qda_gem_obj->sgt = sgt;
+	qda_gem_obj->iommu_dev = iommu_dev;
+	qda_gem_obj->dma_addr = sg_dma_address(sgt->sgl) +
+				qda_soc_sid_offset(qdev->soc_data, iommu_dev->sid);
+
+	return &qda_gem_obj->base;
+}
+
+/**
+ * qda_gem_prime_import() - Import a DMA-BUF into the calling session
+ * @drm_dev: DRM device
+ * @dma_buf: Buffer to import
+ *
+ * A buffer has to be mapped into the context bank of the session importing
+ * it, rather than into the address space of the parent device, so the
+ * attachment is made to that bank.
+ *
+ * drm_gem_prime_fd_to_handle() does not pass the calling file down to this
+ * callback, so the session is recovered from @qda_dev.importing_session,
+ * which qda_prime_fd_to_handle() publishes for the duration of the call.
+ *
+ * Return: The new GEM object on success, ERR_PTR on failure
+ */
+struct drm_gem_object *qda_gem_prime_import(struct drm_device *drm_dev,
+					    struct dma_buf *dma_buf)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
+	struct qda_session *session = qdev->importing_session;
+	struct dma_buf_attachment *attach;
+	struct drm_gem_object *obj;
+	struct sg_table *sgt;
+	int ret;
+
+	if (!session || !session->assigned_iommu_dev)
+		return ERR_PTR(-EINVAL);
+
+	if (drm_gem_is_prime_exported_dma_buf(drm_dev, dma_buf)) {
+		/*
+		 * A buffer this driver exported. The DRM core would hand back
+		 * the original object, which stays in the context bank of the
+		 * session that allocated it, while the DSP of this session
+		 * translates addresses in its own bank. Share the object only
+		 * within a bank, and map it again for any other.
+		 */
+		obj = dma_buf->priv;
+		if (to_qda_gem_obj(obj)->iommu_dev == session->assigned_iommu_dev) {
+			drm_gem_object_get(obj);
+			return obj;
+		}
+	}
+
+	attach = dma_buf_attach(dma_buf, session->assigned_iommu_dev->dev);
+	if (IS_ERR(attach))
+		return ERR_CAST(attach);
+
+	get_dma_buf(dma_buf);
+
+	sgt = dma_buf_map_attachment_unlocked(attach, DMA_BIDIRECTIONAL);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_detach;
+	}
+
+	obj = qda_gem_prime_import_sg_table(drm_dev, attach, sgt);
+	if (IS_ERR(obj)) {
+		ret = PTR_ERR(obj);
+		goto err_unmap;
+	}
+
+	obj->import_attach = attach;
+	obj->resv = dma_buf->resv;
+
+	return obj;
+
+err_unmap:
+	dma_buf_unmap_attachment_unlocked(attach, sgt, DMA_BIDIRECTIONAL);
+err_detach:
+	dma_buf_detach(dma_buf, attach);
+	dma_buf_put(dma_buf);
+
+	return ERR_PTR(ret);
+}
+
+/**
+ * qda_prime_fd_to_handle() - Import a DMA-BUF into a session
+ * @drm_dev: DRM device
+ * @file_priv: DRM file doing the import
+ * @prime_fd: File descriptor of the buffer to import
+ * @handle: Returned handle for the buffer
+ *
+ * Makes sure the session owns a context bank to map the buffer into, then
+ * publishes it for qda_gem_prime_import() to pick up.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_priv,
+			   int prime_fd, u32 *handle)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
+	struct qda_session *session = file_priv->driver_priv;
+	int ret;
+
+	ret = qda_memory_manager_assign_device(qdev->iommu_mgr, session);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&qdev->import_lock);
+
+	qdev->importing_session = session;
+	ret = drm_gem_prime_fd_to_handle(drm_dev, file_priv, prime_fd, handle);
+	qdev->importing_session = NULL;
+
+	return ret;
+}
+
+MODULE_IMPORT_NS("DMA_BUF");
diff --git a/drivers/accel/qda/qda_prime.h b/drivers/accel/qda/qda_prime.h
new file mode 100644
index 000000000000..eb2d8cbc8cd3
--- /dev/null
+++ b/drivers/accel/qda/qda_prime.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_PRIME_H__
+#define __QDA_PRIME_H__
+
+#include <drm/drm_device.h>
+#include <drm/drm_file.h>
+#include <drm/drm_gem.h>
+
+struct dma_buf;
+struct dma_buf_attachment;
+struct sg_table;
+
+struct drm_gem_object *qda_gem_prime_import_sg_table(struct drm_device *drm_dev,
+						     struct dma_buf_attachment *attach,
+						     struct sg_table *sgt);
+struct drm_gem_object *qda_gem_prime_import(struct drm_device *drm_dev,
+					    struct dma_buf *dma_buf);
+int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_priv,
+			   int prime_fd, u32 *handle);
+
+#endif /* __QDA_PRIME_H__ */

-- 
2.34.1


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

* [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (6 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 07/25] accel/qda: Add DMA-BUF import Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:43   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton Ekansh Gupta
                   ` (17 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

The /dev/fastrpc-* interface of the driver this one replaces is to be
served by a separate compatibility module, so existing user space keeps
working. That module needs two things from here: a device per DSP domain
to bind to, and a way to use the same session and buffer handling as the
accel UAPI rather than reimplementing it.

Create an auxiliary device per domain for it to bind, rather than
keeping a private registry and a notifier callback. Domains appear as
each one's rpmsg channel probes and the module may load at any point in
that, which is exactly what the driver core already handles: it matches
devices to the driver whichever order the two arrive in, and autoloads
the module from the device's modalias. The module is optional, so
failing to create the device is not fatal to the accel interface.

The device is destroyed explicitly on rpmsg remove rather than through
devres, so that the legacy nodes are taken away before the DRM device is
unplugged and nothing can open one during teardown.

Collect the entry points the module needs into one table exported only
to it, so this driver does not depend on the module. Start with session
create/destroy and DMA-BUF allocation/import; further ops arrive with
the code that uses them.

A session the module creates has no DRM file behind it, so nothing else
holds the DRM device. Take a reference when it is created, or the device
could be freed on rpmsg remove while a legacy file is still open. For
the same reason, buffers are exported and imported as DMA-BUFs without
a GEM handle, and struct qda_session gains a ->file that is NULL for
such sessions. Code that resolves handles can only be reached from the
accel UAPI.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Kconfig          |  1 +
 drivers/accel/qda/Makefile         |  1 +
 drivers/accel/qda/qda_drv.c        | 48 +++++++++++++++++++++--
 drivers/accel/qda/qda_drv.h        | 31 +++++++++++++--
 drivers/accel/qda/qda_legacy_ops.c | 80 ++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_legacy_ops.h | 41 +++++++++++++++++++
 drivers/accel/qda/qda_prime.c      | 33 ++++++++++++++++
 drivers/accel/qda/qda_prime.h      |  4 ++
 drivers/accel/qda/qda_rpmsg.c      |  3 ++
 9 files changed, 235 insertions(+), 7 deletions(-)

diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
index e2f3f01016c6..c174f9edf2d5 100644
--- a/drivers/accel/qda/Kconfig
+++ b/drivers/accel/qda/Kconfig
@@ -8,6 +8,7 @@ config DRM_ACCEL_QDA
 	depends on DRM_ACCEL
 	depends on ARCH_QCOM || COMPILE_TEST
 	depends on RPMSG
+	select AUXILIARY_BUS
 	help
 	  Enables the DRM-based accelerator driver for Qualcomm's Hexagon DSPs.
 	  This driver provides a standardized interface for offloading computational
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 01c0319da900..a867a888c5f5 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -10,6 +10,7 @@ qda-y := \
 	qda_drv.o \
 	qda_gem.o \
 	qda_ioctl.o \
+	qda_legacy_ops.o \
 	qda_memory_dma.o \
 	qda_memory_manager.o \
 	qda_prime.o \
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 5e953d1f98bf..21113deceaad 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/auxiliary_bus.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <drm/drm_accel.h>
@@ -14,7 +15,13 @@
 #include "qda_ioctl.h"
 #include "qda_prime.h"
 
-static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
+/**
+ * qda_session_alloc() - Create a session on a device
+ * @qdev: Device to create the session on
+ *
+ * Return: The new session, or ERR_PTR on failure
+ */
+struct qda_session *qda_session_alloc(struct qda_dev *qdev)
 {
 	struct qda_session *session;
 
@@ -26,6 +33,7 @@ static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
 
 	return session;
 }
+EXPORT_SYMBOL_FOR_MODULES(qda_session_alloc, "qda_fastrpc_shim");
 
 static int qda_open(struct drm_device *dev, struct drm_file *file)
 {
@@ -42,13 +50,18 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
 	return 0;
 }
 
-static void qda_session_free(struct qda_session *session)
+/**
+ * qda_session_free() - Release a session and whatever it still holds
+ * @session: Session to release
+ */
+void qda_session_free(struct qda_session *session)
 {
 	if (session->assigned_iommu_dev)
 		qda_memory_manager_release_device(session->assigned_iommu_dev);
 
 	kfree(session);
 }
+EXPORT_SYMBOL_FOR_MODULES(qda_session_free, "qda_fastrpc_shim");
 
 static void qda_postclose(struct drm_device *dev, struct drm_file *file)
 {
@@ -117,10 +130,37 @@ int qda_register_device(struct qda_dev *qdev)
 	int ret;
 
 	ret = drm_dev_register(&qdev->drm_dev, 0);
-	if (ret)
+	if (ret) {
 		drm_err(&qdev->drm_dev, "Failed to register DRM device: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * The legacy FastRPC interface is served by a separate module, which
+	 * needs a device per DSP domain to bind to. Put one on the auxiliary
+	 * bus and let the driver core handle discovery, ordering and module
+	 * loading; the module is optional, so failing to create it is not
+	 * fatal to the accel interface.
+	 *
+	 * The auxiliary bus names devices <modname>.<name>.<id> and requires
+	 * the name to be unique across the bus, so every DSP domain needs its
+	 * own id. The accel minor index is unique per registered device.
+	 */
+	qdev->legacy_auxdev = auxiliary_device_create(qdev->dev, KBUILD_MODNAME,
+						      QDA_LEGACY_AUXDEV_NAME, qdev,
+						      qdev->drm_dev.accel->index);
+	if (!qdev->legacy_auxdev)
+		drm_warn(&qdev->drm_dev, "Failed to create legacy FastRPC device\n");
 
-	return ret;
+	return 0;
+}
+
+void qda_unregister_device(struct qda_dev *qdev)
+{
+	if (qdev->legacy_auxdev) {
+		auxiliary_device_destroy(qdev->legacy_auxdev);
+		qdev->legacy_auxdev = NULL;
+	}
 }
 
 MODULE_AUTHOR("Qualcomm AI Infra Team");
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 31aad9057e28..c04f19e5a91b 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -6,6 +6,7 @@
 #ifndef __QDA_DRV_H__
 #define __QDA_DRV_H__
 
+#include <linux/auxiliary_bus.h>
 #include <linux/device.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
@@ -20,15 +21,29 @@
 /* Driver identification */
 #define QDA_DRIVER_NAME "qda"
 
+/*
+ * Name of the auxiliary device the legacy FastRPC module binds to, one per
+ * DSP domain. Combined with this module's name it forms the match name
+ * "qda.fastrpc", which that module lists in its id_table.
+ */
+#define QDA_LEGACY_AUXDEV_NAME "fastrpc"
+
 /**
- * struct qda_session - Per-open-file private data
+ * struct qda_session - One user of a DSP
  *
- * Allocated in qda_open() and stored in drm_file::driver_priv.
+ * Created either by the accel UAPI, one per open DRM file, or by the legacy
+ * FastRPC compatibility shim, one per open /dev/fastrpc-* file with no DRM
+ * file behind it at all. The core is keyed on this rather than on struct
+ * drm_file so that it can serve both.
  */
 struct qda_session {
 	/** @qda_dev: Back-pointer to device structure */
 	struct qda_dev *qda_dev;
-	/** @file: The drm_file this session belongs to */
+	/**
+	 * @file: The DRM file this session belongs to, or NULL for a session
+	 * the legacy shim created. Anything that resolves a buffer handle
+	 * needs this, so those paths are only reachable from the accel UAPI.
+	 */
 	struct drm_file *file;
 	/** @assigned_iommu_dev: Context bank owned by this session, or NULL */
 	struct qda_iommu_device *assigned_iommu_dev;
@@ -64,6 +79,11 @@ struct qda_dev {
 	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
 	const char *dsp_name;
+	/**
+	 * @legacy_auxdev: Auxiliary device the legacy FastRPC module binds
+	 * to, or NULL if it could not be created
+	 */
+	struct auxiliary_device *legacy_auxdev;
 };
 
 /**
@@ -84,5 +104,10 @@ struct qda_dev *qda_alloc_device(struct device *dev);
 int qda_init_device(struct qda_dev *qdev, int num_cbs);
 void qda_deinit_device(struct qda_dev *qdev);
 int qda_register_device(struct qda_dev *qdev);
+void qda_unregister_device(struct qda_dev *qdev);
+
+/* Sessions, shared with the legacy shim */
+struct qda_session *qda_session_alloc(struct qda_dev *qdev);
+void qda_session_free(struct qda_session *session);
 
 #endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
new file mode 100644
index 000000000000..3566a10c6488
--- /dev/null
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-buf.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/fcntl.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_legacy_ops.h"
+#include "qda_prime.h"
+
+/*
+ * A session the shim creates has no DRM file behind it, and so nothing
+ * holding the DRM device. Take a reference here: the device is allocated
+ * against the rpmsg device and would otherwise be freed on remove while a
+ * /dev/fastrpc-* file is still open on it.
+ */
+static struct qda_session *legacy_session_create(struct qda_dev *qdev)
+{
+	struct qda_session *session;
+
+	session = qda_session_alloc(qdev);
+	if (!IS_ERR(session))
+		drm_dev_get(&qdev->drm_dev);
+
+	return session;
+}
+
+static void legacy_session_destroy(struct qda_session *session)
+{
+	struct drm_device *drm_dev = &session->qda_dev->drm_dev;
+
+	qda_session_free(session);
+	drm_dev_put(drm_dev);
+}
+
+/*
+ * 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
+ * legacy interface hands buffers to user space as file descriptors anyway.
+ */
+static struct dma_buf *legacy_alloc_dmabuf(struct qda_session *session, u64 size)
+{
+	struct qda_dev *qdev = session->qda_dev;
+	struct drm_gem_object *gem_obj;
+	struct dma_buf *dmabuf;
+
+	gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr, size, session);
+	if (IS_ERR(gem_obj))
+		return ERR_CAST(gem_obj);
+
+	/* O_RDWR so the fd can be mapped writable */
+	dmabuf = drm_gem_prime_export(gem_obj, O_RDWR);
+
+	/* The DMA-BUF took its own reference if the export succeeded */
+	drm_gem_object_put(gem_obj);
+
+	return dmabuf;
+}
+
+/*
+ * As the PRIME import path, without the handle: same reason as above. The
+ * caller owns the returned reference.
+ */
+static struct drm_gem_object *legacy_import_dmabuf(struct qda_session *session,
+						   struct dma_buf *dmabuf)
+{
+	return qda_prime_import_dmabuf(&session->qda_dev->drm_dev, session, dmabuf);
+}
+
+const struct qda_legacy_ops qda_legacy_ops_table = {
+	.session_create = legacy_session_create,
+	.session_destroy = legacy_session_destroy,
+	.alloc_dmabuf = legacy_alloc_dmabuf,
+	.import_dmabuf = legacy_import_dmabuf,
+};
+EXPORT_SYMBOL_FOR_MODULES(qda_legacy_ops_table, "qda_fastrpc_shim");
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
new file mode 100644
index 000000000000..76b80dc87a4b
--- /dev/null
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_LEGACY_OPS_H__
+#define __QDA_LEGACY_OPS_H__
+
+#include <linux/types.h>
+
+struct dma_buf;
+struct drm_gem_object;
+struct qda_dev;
+struct qda_session;
+
+/**
+ * struct qda_legacy_ops - What the legacy FastRPC shim may call
+ *
+ * The shim serves the /dev/fastrpc-* interface of the driver this one
+ * replaces, on top of the same session and buffer handling the accel UAPI
+ * uses. It is a separate module, so the entry points it needs are collected
+ * here and exported as one table: that way the driver does not depend on the
+ * shim, and neither module has to reimplement what the other does.
+ *
+ * Ops are added as the shim grows to need them.
+ */
+struct qda_legacy_ops {
+	/** @session_create: Open a session on @qdev */
+	struct qda_session *(*session_create)(struct qda_dev *qdev);
+	/** @session_destroy: Close a session */
+	void (*session_destroy)(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 */
+	struct drm_gem_object *(*import_dmabuf)(struct qda_session *session,
+						struct dma_buf *dmabuf);
+};
+
+extern const struct qda_legacy_ops qda_legacy_ops_table;
+
+#endif /* __QDA_LEGACY_OPS_H__ */
diff --git a/drivers/accel/qda/qda_prime.c b/drivers/accel/qda/qda_prime.c
index 7a709e3000d8..128d4ab2cf64 100644
--- a/drivers/accel/qda/qda_prime.c
+++ b/drivers/accel/qda/qda_prime.c
@@ -181,4 +181,37 @@ int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_pri
 	return ret;
 }
 
+/**
+ * qda_prime_import_dmabuf() - Import a DMA-BUF without installing a handle
+ * @drm_dev: DRM device
+ * @session: Session to import into
+ * @dma_buf: Buffer to import
+ *
+ * As qda_prime_fd_to_handle(), but stopping at the GEM object: a session the
+ * legacy shim created has no DRM file to install a handle in. The caller owns
+ * the returned reference.
+ *
+ * Return: The new GEM object on success, ERR_PTR on failure
+ */
+struct drm_gem_object *qda_prime_import_dmabuf(struct drm_device *drm_dev,
+					       struct qda_session *session,
+					       struct dma_buf *dma_buf)
+{
+	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
+	struct drm_gem_object *gem_obj;
+	int ret;
+
+	ret = qda_memory_manager_assign_device(qdev->iommu_mgr, session);
+	if (ret)
+		return ERR_PTR(ret);
+
+	guard(mutex)(&qdev->import_lock);
+
+	qdev->importing_session = session;
+	gem_obj = qda_gem_prime_import(drm_dev, dma_buf);
+	qdev->importing_session = NULL;
+
+	return gem_obj;
+}
+
 MODULE_IMPORT_NS("DMA_BUF");
diff --git a/drivers/accel/qda/qda_prime.h b/drivers/accel/qda/qda_prime.h
index eb2d8cbc8cd3..c5e2f7ffa882 100644
--- a/drivers/accel/qda/qda_prime.h
+++ b/drivers/accel/qda/qda_prime.h
@@ -12,6 +12,7 @@
 
 struct dma_buf;
 struct dma_buf_attachment;
+struct qda_session;
 struct sg_table;
 
 struct drm_gem_object *qda_gem_prime_import_sg_table(struct drm_device *drm_dev,
@@ -21,5 +22,8 @@ struct drm_gem_object *qda_gem_prime_import(struct drm_device *drm_dev,
 					    struct dma_buf *dma_buf);
 int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_priv,
 			   int prime_fd, u32 *handle);
+struct drm_gem_object *qda_prime_import_dmabuf(struct drm_device *drm_dev,
+					       struct qda_session *session,
+					       struct dma_buf *dma_buf);
 
 #endif /* __QDA_PRIME_H__ */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 2c9d79937b10..14034e250555 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -50,6 +50,9 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
 {
 	struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
 
+	/* Take the legacy nodes away first, so nothing new opens during teardown */
+	qda_unregister_device(qdev);
+
 	/*
 	 * Unplug before clearing @rpdev: once there are ioctls that reach the
 	 * DSP, this is what stops a new one from starting and waits for the

-- 
2.34.1


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

* [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (7 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:42   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts Ekansh Gupta
                   ` (16 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Existing FastRPC user space talks to /dev/fastrpc-<dsp> through the ioctl
interface of the driver in drivers/misc/. Add a separate module that
serves that interface on top of this driver, so such user space keeps
working while it moves to the accel UAPI. It is separate so that the
driver itself does not depend on it, and so it can be left out.

The module binds the auxiliary device the driver creates per DSP domain
and gives that domain its misc node(s). As in the driver it replaces, a
channel is secure unless its DT node has qcom,non-secure-domain. ADSP,
MDSP and SDSP get a single node, secure or not according to the channel.
CDSP and GDSP support unsigned PD offload, so they always get both a
secure and a non-secure node, letting untrusted applications use the
latter on a secure channel. Nothing distinguishes the two nodes yet;
that is applied to the operations that need it as they are added.

Each open file gets a session from the driver, which also pins the DRM
device for as long as the file stays open.

Only FASTRPC_IOCTL_ALLOC_DMA_BUFF is implemented here: it allocates a
buffer and returns it as a DMA-BUF fd. FASTRPC_IOCTL_FREE_DMA_BUFF
returns -ENOTTY, as in the driver this replaces; buffers are released by
closing the fd. The fd is installed only once nothing else can fail.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Kconfig            |  16 +++
 drivers/accel/qda/Makefile           |   2 +
 drivers/accel/qda/qda_fastrpc_shim.c | 268 +++++++++++++++++++++++++++++++++++
 3 files changed, 286 insertions(+)

diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
index c174f9edf2d5..c440191a54ff 100644
--- a/drivers/accel/qda/Kconfig
+++ b/drivers/accel/qda/Kconfig
@@ -26,3 +26,19 @@ config DRM_ACCEL_QDA
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called qda.
+
+config DRM_ACCEL_QDA_FASTRPC_SHIM
+	tristate "Qualcomm DSP accelerator legacy FastRPC compatibility shim"
+	depends on DRM_ACCEL_QDA
+	default DRM_ACCEL_QDA
+	help
+	  Exposes a /dev/fastrpc-<dsp> character device per DSP domain, serving
+	  the ioctl interface of the FastRPC driver in drivers/misc/ on top of
+	  the QDA driver, so that existing FastRPC user space keeps working.
+
+	  This is a compatibility layer for user space that has not moved to
+	  the native interface in include/uapi/drm/qda_accel.h; new users
+	  should use that instead.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called qda_fastrpc_shim.
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index a867a888c5f5..0996e4ed5798 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -16,3 +16,5 @@ qda-y := \
 	qda_prime.o \
 	qda_rpmsg.o \
 	qda_soc.o
+
+obj-$(CONFIG_DRM_ACCEL_QDA_FASTRPC_SHIM) += qda_fastrpc_shim.o
diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
new file mode 100644
index 000000000000..25f5c242efde
--- /dev/null
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/array_size.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/dma-buf.h>
+#include <linux/err.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/limits.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <drm/drm_drv.h>
+#include <uapi/misc/fastrpc.h>
+#include "qda_drv.h"
+#include "qda_legacy_ops.h"
+
+/* State of one open /dev/fastrpc-* file */
+struct qda_shim_file {
+	struct qda_session *session;
+};
+
+/* One misc device node */
+struct qda_shim_node {
+	struct qda_shim_dev *sdev;
+	struct miscdevice miscdev;
+	char *name;
+	bool registered;
+};
+
+/*
+ * One per DSP domain, holding up to two misc nodes. As in
+ * drivers/misc/fastrpc.c, a channel is secure unless its DT node has
+ * qcom,non-secure-domain. ADSP, MDSP and SDSP get a single node, secure or
+ * not according to the channel. CDSP and GDSP, which support unsigned PD
+ * 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.
+ */
+struct qda_shim_dev {
+	struct qda_dev *qdev;
+	struct qda_shim_node nodes[2];
+};
+
+static const struct file_operations qda_shim_fops;
+
+static int qda_shim_open(struct inode *inode, struct file *file)
+{
+	struct qda_shim_node *snode = container_of(file->private_data, struct qda_shim_node,
+						   miscdev);
+	struct qda_shim_file *sf;
+
+	sf = kzalloc_obj(*sf);
+	if (!sf)
+		return -ENOMEM;
+
+	sf->session = qda_legacy_ops_table.session_create(snode->sdev->qdev);
+	if (IS_ERR(sf->session)) {
+		int err = PTR_ERR(sf->session);
+
+		kfree(sf);
+		return err;
+	}
+
+	file->private_data = sf;
+
+	return 0;
+}
+
+static int qda_shim_release(struct inode *inode, struct file *file)
+{
+	struct qda_shim_file *sf = file->private_data;
+
+	qda_legacy_ops_table.session_destroy(sf->session);
+	kfree(sf);
+
+	return 0;
+}
+
+static long qda_shim_alloc_dma_buf(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_alloc_dma_buf args;
+	struct dma_buf *dmabuf;
+	int fd;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	/* The size is 64 bits wide on the wire but a size_t further down */
+	if (args.size > SIZE_MAX)
+		return -EINVAL;
+
+	/* args.flags is not used, as in drivers/misc/fastrpc.c */
+	dmabuf = qda_legacy_ops_table.alloc_dmabuf(sf->session, args.size);
+	if (IS_ERR(dmabuf))
+		return PTR_ERR(dmabuf);
+
+	fd = get_unused_fd_flags(O_CLOEXEC);
+	if (fd < 0) {
+		dma_buf_put(dmabuf);
+		return fd;
+	}
+
+	args.fd = fd;
+	if (copy_to_user(argp, &args, sizeof(args))) {
+		put_unused_fd(fd);
+		dma_buf_put(dmabuf);
+		return -EFAULT;
+	}
+
+	/* Publish the fd only once nothing can fail */
+	fd_install(fd, dmabuf->file);
+
+	return 0;
+}
+
+static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void __user *argp)
+{
+	switch (cmd) {
+	case FASTRPC_IOCTL_ALLOC_DMA_BUFF:
+		return qda_shim_alloc_dma_buf(sf, argp);
+	default:
+		/*
+		 * Includes FASTRPC_IOCTL_FREE_DMA_BUFF, which the driver this
+		 * replaces never implemented either: buffers are released by
+		 * closing their fd.
+		 */
+		return -ENOTTY;
+	}
+}
+
+/*
+ * Mirrors drm_ioctl()'s unplug check. Not drm_dev_enter(): holding the SRCU
+ * read side across a wait for the DSP's reply would stall drm_dev_unplug()
+ * in qda_rpmsg_remove() for as long as the DSP takes to answer.
+ */
+static long qda_shim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct qda_shim_file *sf = file->private_data;
+
+	if (drm_dev_is_unplugged(&sf->session->qda_dev->drm_dev))
+		return -ENODEV;
+
+	return qda_shim_dispatch(sf, cmd, (void __user *)arg);
+}
+
+static const struct file_operations qda_shim_fops = {
+	.owner = THIS_MODULE,
+	.open = qda_shim_open,
+	.release = qda_shim_release,
+	.unlocked_ioctl = qda_shim_ioctl,
+	.compat_ioctl = compat_ptr_ioctl,
+};
+
+static int qda_shim_node_register(struct qda_shim_dev *sdev, int i, bool secure)
+{
+	struct qda_shim_node *snode = &sdev->nodes[i];
+	int err;
+
+	snode->name = kasprintf(GFP_KERNEL, "fastrpc-%s%s", sdev->qdev->dsp_name,
+				secure ? "-secure" : "");
+	if (!snode->name)
+		return -ENOMEM;
+
+	snode->sdev = sdev;
+	snode->miscdev.minor = MISC_DYNAMIC_MINOR;
+	snode->miscdev.name = snode->name;
+	snode->miscdev.fops = &qda_shim_fops;
+
+	err = misc_register(&snode->miscdev);
+	if (err) {
+		kfree(snode->name);
+		snode->name = NULL;
+		return err;
+	}
+
+	snode->registered = true;
+
+	return 0;
+}
+
+/*
+ * misc_deregister() takes misc_mtx, which misc_open() holds across the
+ * lookup and our ->open(). So once it returns no open can still be using
+ * the node. Files that are already open hold neither the node nor @sdev,
+ * only their session.
+ */
+static void qda_shim_nodes_unregister(struct qda_shim_dev *sdev)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sdev->nodes); i++) {
+		if (sdev->nodes[i].registered)
+			misc_deregister(&sdev->nodes[i].miscdev);
+		kfree(sdev->nodes[i].name);
+	}
+}
+
+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;
+
+	sdev = devm_kzalloc(&auxdev->dev, sizeof(*sdev), GFP_KERNEL);
+	if (!sdev)
+		return -ENOMEM;
+
+	sdev->qdev = qdev;
+
+	if (!strncmp(qdev->dsp_name, "cdsp", 4) || !strncmp(qdev->dsp_name, "gdsp", 4)) {
+		/* Both nodes, so signed and unsigned PDs are both reachable */
+		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);
+	} else {
+		err = -ENODEV;
+	}
+
+	if (err) {
+		dev_err(&auxdev->dev, "Failed to create %s node(s): %d\n",
+			qdev->dsp_name, err);
+		qda_shim_nodes_unregister(sdev);
+		return err;
+	}
+
+	auxiliary_set_drvdata(auxdev, sdev);
+
+	return 0;
+}
+
+static void qda_shim_remove(struct auxiliary_device *auxdev)
+{
+	qda_shim_nodes_unregister(auxiliary_get_drvdata(auxdev));
+}
+
+static const struct auxiliary_device_id qda_shim_id_table[] = {
+	{ .name = QDA_DRIVER_NAME "." QDA_LEGACY_AUXDEV_NAME },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, qda_shim_id_table);
+
+static struct auxiliary_driver qda_shim_driver = {
+	.name = "fastrpc_shim",
+	.id_table = qda_shim_id_table,
+	.probe = qda_shim_probe,
+	.remove = qda_shim_remove,
+};
+
+module_auxiliary_driver(qda_shim_driver);
+
+MODULE_IMPORT_NS("DMA_BUF");
+MODULE_AUTHOR("Qualcomm AI Infra Team");
+MODULE_DESCRIPTION("Qualcomm DSP accelerator legacy FastRPC compatibility shim");
+MODULE_LICENSE("GPL");

-- 
2.34.1


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

* [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (8 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:44   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch Ekansh Gupta
                   ` (15 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Running something on the DSP means sending it a message and waiting for
the reply. Add the bookkeeping for an invocation that has been sent and
not yet answered, before there is anything to send.

The DSP identifies an invocation by a context value it echoes back in
its response, so each invocation gets an entry in a per-device lookup
table and the entry's index is placed in that value. The index is only 8
bits wide and is reused as soon as its invocation is freed, so a late or
duplicate response could otherwise name an ID that now belongs to a
different invocation and complete it early, with its caller then reading
a buffer the DSP is still writing. To tell the two apart, each context
also carries a sequence number that does not repeat in practice, for the
response handler to check.

An invocation is removed from the table only when its last reference is
dropped, never when its caller gives up waiting, so an ID cannot be
reused while the old invocation can still be found and completed. The
number of invocations in flight on one device is bounded by the size of
the ID space, which also bounds what they can allocate.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/Makefile      |  1 +
 drivers/accel/qda/qda_drv.c     |  3 ++
 drivers/accel/qda/qda_drv.h     |  6 ++++
 drivers/accel/qda/qda_fastrpc.c | 63 +++++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h | 54 +++++++++++++++++++++++++++++++++++
 5 files changed, 127 insertions(+)

diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 0996e4ed5798..aee69cd7d150 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_legacy_ops.o \
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 21113deceaad..bdfb23c3c0b1 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -100,6 +100,8 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 
 	INIT_LIST_HEAD(&qdev->cb_devs);
 	mutex_init(&qdev->import_lock);
+	xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
+	atomic64_set(&qdev->ctx_seq, 0);
 
 	return qdev;
 }
@@ -122,6 +124,7 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
 void qda_deinit_device(struct qda_dev *qdev)
 {
 	qda_memory_manager_exit(qdev->iommu_mgr);
+	xa_destroy(&qdev->ctx_xa);
 	mutex_destroy(&qdev->import_lock);
 }
 
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index c04f19e5a91b..b7cbb1667adb 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -6,12 +6,14 @@
 #ifndef __QDA_DRV_H__
 #define __QDA_DRV_H__
 
+#include <linux/atomic.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/device.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/rpmsg.h>
 #include <linux/types.h>
+#include <linux/xarray.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
@@ -75,6 +77,10 @@ struct qda_dev {
 	 * calling file to.
 	 */
 	struct qda_session *importing_session;
+	/** @ctx_xa: Invocations in flight, keyed by context ID */
+	struct xarray ctx_xa;
+	/** @ctx_seq: Distinguishes invocations that reuse a context ID */
+	atomic64_t ctx_seq;
 	/** @soc_data: Per-SoC DSP integration parameters */
 	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
new file mode 100644
index 000000000000..14d9da8c20b3
--- /dev/null
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/bitfield.h>
+#include <linux/slab.h>
+#include "qda_drv.h"
+#include "qda_fastrpc.h"
+
+/**
+ * qda_fastrpc_context_free() - Release an invocation
+ * @ref: Reference counter embedded in the invocation
+ *
+ * Runs when the last reference goes. The invocation leaves the lookup table
+ * here and not earlier, so that its ID cannot be handed to a new invocation
+ * while this one can still be found and completed.
+ */
+void qda_fastrpc_context_free(struct kref *ref)
+{
+	struct qda_fastrpc_invoke_ctx *ctx =
+		container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);
+
+	xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid));
+	kfree(ctx);
+}
+
+/**
+ * qda_fastrpc_context_alloc() - Allocate an invocation and give it an ID
+ * @qdev: Device the invocation is issued on
+ *
+ * The caller holds the initial reference and drops it with
+ * qda_fastrpc_context_free().
+ *
+ * Return: The new invocation, or ERR_PTR on failure
+ */
+struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
+{
+	struct qda_fastrpc_invoke_ctx *ctx;
+	u32 id;
+	int ret;
+
+	ctx = kzalloc_obj(*ctx);
+	if (!ctx)
+		return ERR_PTR(-ENOMEM);
+
+	init_completion(&ctx->work);
+	kref_init(&ctx->refcount);
+	ctx->qdev = qdev;
+	/* Anything but a status the DSP could send, until it answers */
+	ctx->retval = -1;
+
+	/* ID 0 is never used, so a zeroed context in a response matches nothing */
+	ret = xa_alloc(&qdev->ctx_xa, &id, ctx, XA_LIMIT(1, QDA_FASTRPC_CTXID_MAX),
+		       GFP_KERNEL);
+	if (ret) {
+		kfree(ctx);
+		return ERR_PTR(ret);
+	}
+
+	ctx->ctxid = FIELD_PREP(QDA_FASTRPC_CTXID_MASK, id) |
+		     FIELD_PREP(QDA_FASTRPC_CTXID_SEQ_MASK,
+				(u64)atomic64_inc_return(&qdev->ctx_seq));
+
+	return ctx;
+}
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
new file mode 100644
index 000000000000..face3b3336b6
--- /dev/null
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -0,0 +1,54 @@
+/* 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/bits.h>
+#include <linux/completion.h>
+#include <linux/kref.h>
+#include <linux/types.h>
+
+struct qda_dev;
+
+/*
+ * Layout of the context the DSP echoes back in its response:
+ *   [63:16] sequence number
+ *   [15:8]  context ID, the key in the device's lookup table
+ *   [7:0]   reserved for the protection domain, set when the message is built
+ *
+ * The context ID is narrow and is reused as soon as its invocation is freed,
+ * so a late or duplicate response could name an ID that now belongs to a
+ * newer invocation. The sequence number, which never repeats in practice,
+ * lets the response handler tell the two apart.
+ */
+#define QDA_FASTRPC_CTXID_MASK		GENMASK_ULL(15, 8)
+#define QDA_FASTRPC_CTXID_SEQ_MASK	GENMASK_ULL(63, 16)
+#define QDA_FASTRPC_CTXID_MAX		(QDA_FASTRPC_CTXID_MASK >> 8)
+
+/**
+ * struct qda_fastrpc_invoke_ctx - One invocation in flight
+ */
+struct qda_fastrpc_invoke_ctx {
+	/** @qdev: Device the invocation was issued on */
+	struct qda_dev *qdev;
+	/** @ctxid: Wire context with the protection domain bits clear */
+	u64 ctxid;
+	/** @retval: Status the DSP reported, or an error if it never answered */
+	int retval;
+	/** @work: Completed once the DSP has answered */
+	struct completion work;
+	/**
+	 * @refcount: Keeps the invocation alive for everyone who may still
+	 * touch it: the caller and, once a message is sent, the response
+	 * handler.
+	 */
+	struct kref refcount;
+};
+
+struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev);
+void qda_fastrpc_context_free(struct kref *ref);
+
+#endif /* __QDA_FASTRPC_H__ */

-- 
2.34.1


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

* [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (9 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:39   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing Ekansh Gupta
                   ` (14 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

An invocation reaches the DSP as an rpmsg message, and the DSP answers
with a second message naming the invocation. Add both directions, along
with the wire message itself and the kernel-side bookkeeping that is
carried alongside it.

Sending takes a reference on the invocation for the response handler, so
the invocation stays alive even if its caller stops waiting for the
answer. If the send fails, that reference is dropped again here, because
no response will come to drop it.

A response is data the DSP wrote, so the handler checks that the whole
message is there before reading any of it, then looks the invocation up
by the context ID the DSP echoed. Context IDs are narrow and get reused,
so a late or duplicate response can name an ID that now belongs to a
newer invocation. The handler compares the sequence number as well and
drops a response that does not match, rather than completing the wrong
invocation early.

The handler runs in atomic context, and dropping the last reference
frees the context's DMA buffers, so that reference is dropped from a
worker instead.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/qda_fastrpc.c |   9 ++++
 drivers/accel/qda/qda_fastrpc.h |  44 +++++++++++++++++
 drivers/accel/qda/qda_rpmsg.c   | 107 ++++++++++++++++++++++++++++++++++++++--
 drivers/accel/qda/qda_rpmsg.h   |  26 ++++++++++
 4 files changed, 182 insertions(+), 4 deletions(-)

diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 14d9da8c20b3..ec37a38e9215 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -22,6 +22,14 @@ void qda_fastrpc_context_free(struct kref *ref)
 	kfree(ctx);
 }
 
+static void qda_fastrpc_context_put_wq(struct work_struct *work)
+{
+	struct qda_fastrpc_invoke_ctx *ctx =
+		container_of(work, struct qda_fastrpc_invoke_ctx, put_work);
+
+	kref_put(&ctx->refcount, qda_fastrpc_context_free);
+}
+
 /**
  * qda_fastrpc_context_alloc() - Allocate an invocation and give it an ID
  * @qdev: Device the invocation is issued on
@@ -42,6 +50,7 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
 		return ERR_PTR(-ENOMEM);
 
 	init_completion(&ctx->work);
+	INIT_WORK(&ctx->put_work, qda_fastrpc_context_put_wq);
 	kref_init(&ctx->refcount);
 	ctx->qdev = qdev;
 	/* Anything but a status the DSP could send, until it answers */
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index face3b3336b6..170b513533b9 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -10,6 +10,7 @@
 #include <linux/completion.h>
 #include <linux/kref.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 
 struct qda_dev;
 
@@ -28,6 +29,43 @@ struct qda_dev;
 #define QDA_FASTRPC_CTXID_SEQ_MASK	GENMASK_ULL(63, 16)
 #define QDA_FASTRPC_CTXID_MAX		(QDA_FASTRPC_CTXID_MASK >> 8)
 
+/**
+ * struct qda_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.
+ */
+struct qda_fastrpc_msg {
+	/** @remote_session_id: Session identifier on the remote processor */
+	int remote_session_id;
+	/** @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;
+} __packed;
+
+/**
+ * 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 qda_fastrpc_msg fastrpc;
+	/** @fastrpc_ctx: Back-pointer to the owning invocation context */
+	struct qda_fastrpc_invoke_ctx *fastrpc_ctx;
+};
+
 /**
  * struct qda_fastrpc_invoke_ctx - One invocation in flight
  */
@@ -46,6 +84,12 @@ struct qda_fastrpc_invoke_ctx {
 	 * handler.
 	 */
 	struct kref refcount;
+	/**
+	 * @put_work: Drops the response callback's reference from process
+	 * context, since the callback runs in atomic context and the final
+	 * put frees DMA memory.
+	 */
+	struct work_struct put_work;
 };
 
 struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev);
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 14034e250555..43b56ab3f012 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/bitfield.h>
+#include <linux/completion.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/rpmsg.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"
 #include "qda_soc.h"
 
 /*
@@ -36,14 +41,108 @@ static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
 	return qdev;
 }
 
-/*
- * The rpmsg core requires a callback, so one is registered from the start.
- * Responses are dispatched here once there are invocations to answer.
+/**
+ * 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)
 {
-	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, FIELD_GET(QDA_FASTRPC_CTXID_MASK, resp_msg->ctx));
+	if (!ctx) {
+		drm_dbg_driver(&qdev->drm_dev, "No context matches response 0x%llx\n",
+			       resp_msg->ctx);
+		ret = -ENOENT;
+		goto out_exit;
+	}
+
+	/*
+	 * Context IDs are only 8 bits wide and are recycled once a context is
+	 * freed, so a late or duplicate response may name an ID that now belongs
+	 * to a newer context. The sequence number tells the two apart.
+	 */
+	if (FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, resp_msg->ctx) !=
+	    FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, ctx->ctxid)) {
+		drm_dbg_driver(&qdev->drm_dev, "Dropping stale response 0x%llx\n",
+			       resp_msg->ctx);
+		goto out_exit;
+	}
+
+	ctx->retval = resp_msg->retval;
+	complete(&ctx->work);
+
+	/*
+	 * Release the reference taken by qda_rpmsg_send_msg(). This callback
+	 * runs in atomic context, and if it drops the last reference the
+	 * context's DMA buffers are freed, which must not happen here.
+	 */
+	schedule_work(&ctx->put_work);
+
+out_exit:
+	drm_dev_exit(idx);
+
+	return ret;
 }
 
 static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
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__ */

-- 
2.34.1


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

* [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (10 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:44   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug Ekansh Gupta
                   ` (13 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

The DSP reads one buffer per invocation describing where every argument
is. Build that buffer from the argument descriptors user space supplies.

A descriptor names a buffer by handle and a region of it by offset and
length. Handles are resolved in the calling session's own table, and the
buffer they name must be mapped in the context bank assigned to that
session. The table alone is not enough: a buffer exported with
DRM_IOCTL_PRIME_HANDLE_TO_FD and imported back on the same device
resolves to the original object, still mapped in the exporting session's
bank, and the DSP derives the bank from the address it is given. Without
the second check one session could read and write in another's address
space.

For a buffer argument the region is also checked against the buffer's
real size. A region described as larger than its buffer would otherwise
let the DSP read and write past the end of it, into whatever else is
mapped in the same context bank.

The DSP is given the exact address of each argument and the page-aligned
region containing it, since it takes the offset within the page from the
low bits of the address.

The payload is cleared before use, so nothing left from a previous
allocation is visible to the DSP, and a write barrier separates filling
it in from telling the DSP about it.

The same buffer carries a list the DSP writes into to say which buffers
it has finished with, so releasing those is added here alongside the
code that decides where the list lives. That memory is writable by the
DSP, so its contents are not trusted: the walk is bounded by the region
sized for it, stops at the first empty entry, and resolves each entry in
the calling session's own handle table, so a bad value can at worst name
a buffer that session already owns. It is only read once a barrier has
made the DSP's writes visible.

Buffers resolved for an invocation are held until it is freed, so an
error part-way through releases what was already resolved.

An invocation keeps a reference on the session that issued it. A caller
that stops waiting for the DSP leaves the invocation behind, because the
DSP still owns the payload buffer, and that buffer was allocated from
the session's context bank. Holding the session keeps the bank assigned
for as long as the DSP may still write into it, and keeps the handle
table around for the list above.

Payload buffers are kept per session rather than freed, and reused by a
later invocation needing one no larger, since allocating and tearing
down a DMA-coherent, IOMMU-mapped buffer for every invocation is wasteful
for a workload that invokes the same handle repeatedly. The number kept
is bounded, so a session cannot pin an unbounded number of buffers, and
the rest are freed with the session.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c        |  49 +++-
 drivers/accel/qda/qda_drv.h        |  23 +-
 drivers/accel/qda/qda_fastrpc.c    | 482 +++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h    | 111 +++++++++
 drivers/accel/qda/qda_legacy_ops.c |   2 +-
 include/uapi/drm/qda_accel.h       |  36 +++
 6 files changed, 695 insertions(+), 8 deletions(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index bdfb23c3c0b1..99bd37fca438 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -12,6 +12,7 @@
 #include <drm/qda_accel.h>
 
 #include "qda_drv.h"
+#include "qda_fastrpc.h"
 #include "qda_ioctl.h"
 #include "qda_prime.h"
 
@@ -19,6 +20,8 @@
  * qda_session_alloc() - Create a session on a device
  * @qdev: Device to create the session on
  *
+ * The caller holds the initial reference and drops it with qda_session_put().
+ *
  * Return: The new session, or ERR_PTR on failure
  */
 struct qda_session *qda_session_alloc(struct qda_dev *qdev)
@@ -30,6 +33,14 @@ struct qda_session *qda_session_alloc(struct qda_dev *qdev)
 		return ERR_PTR(-ENOMEM);
 
 	session->qda_dev = qdev;
+	/*
+	 * Wraps after 2^31 sessions on one device, which no real workload
+	 * reaches: a session lasts as long as the file it was opened on.
+	 */
+	session->remote_session_id = atomic_inc_return(&qdev->session_id_seq);
+	INIT_LIST_HEAD(&session->msg_buf_pool);
+	mutex_init(&session->msg_buf_pool_lock);
+	kref_init(&session->ref);
 
 	return session;
 }
@@ -50,22 +61,47 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
 	return 0;
 }
 
-/**
- * qda_session_free() - Release a session and whatever it still holds
- * @session: Session to release
+/*
+ * Runs once the owner and every invocation of the session are gone, so the
+ * context bank is held for as long as the DSP may still write into a buffer
+ * allocated from it.
  */
-void qda_session_free(struct qda_session *session)
+static void qda_session_release(struct kref *ref)
 {
+	struct qda_session *session = container_of(ref, struct qda_session, ref);
+
+	qda_session_msg_buf_pool_destroy(session);
+
 	if (session->assigned_iommu_dev)
 		qda_memory_manager_release_device(session->assigned_iommu_dev);
 
+	mutex_destroy(&session->msg_buf_pool_lock);
 	kfree(session);
 }
-EXPORT_SYMBOL_FOR_MODULES(qda_session_free, "qda_fastrpc_shim");
+
+/**
+ * qda_session_get() - Take a reference on a session
+ * @session: Session to reference
+ */
+void qda_session_get(struct qda_session *session)
+{
+	kref_get(&session->ref);
+}
+EXPORT_SYMBOL_FOR_MODULES(qda_session_get, "qda_fastrpc_shim");
+
+/**
+ * qda_session_put() - Drop a reference on a session
+ * @session: Session to release
+ */
+void qda_session_put(struct qda_session *session)
+{
+	kref_put(&session->ref, qda_session_release);
+}
+EXPORT_SYMBOL_FOR_MODULES(qda_session_put, "qda_fastrpc_shim");
 
 static void qda_postclose(struct drm_device *dev, struct drm_file *file)
 {
-	qda_session_free(file->driver_priv);
+	qda_session_put(file->driver_priv);
 }
 
 DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
@@ -102,6 +138,7 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 	mutex_init(&qdev->import_lock);
 	xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
 	atomic64_set(&qdev->ctx_seq, 0);
+	atomic_set(&qdev->session_id_seq, 0);
 
 	return qdev;
 }
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index b7cbb1667adb..80a8569b311a 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -9,6 +9,7 @@
 #include <linux/atomic.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/device.h>
+#include <linux/kref.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/rpmsg.h>
@@ -37,6 +38,10 @@
  * FastRPC compatibility shim, one per open /dev/fastrpc-* file with no DRM
  * file behind it at all. The core is keyed on this rather than on struct
  * drm_file so that it can serve both.
+ *
+ * Reference counted because an invocation outlives the caller that issued it
+ * when the DSP has not answered yet, and keeps using the session it belongs
+ * to after the file it was opened on is closed.
  */
 struct qda_session {
 	/** @qda_dev: Back-pointer to device structure */
@@ -49,6 +54,19 @@ struct qda_session {
 	struct drm_file *file;
 	/** @assigned_iommu_dev: Context bank owned by this session, or NULL */
 	struct qda_iommu_device *assigned_iommu_dev;
+	/** @remote_session_id: Identifies this session to the DSP */
+	int remote_session_id;
+	/**
+	 * @msg_buf_pool: Invoke header buffers this session is done with,
+	 * kept for its later invocations to reuse.
+	 */
+	struct list_head msg_buf_pool;
+	/** @msg_buf_pool_count: Number of entries in @msg_buf_pool */
+	int msg_buf_pool_count;
+	/** @msg_buf_pool_lock: Protects @msg_buf_pool and @msg_buf_pool_count */
+	struct mutex msg_buf_pool_lock;
+	/** @ref: Dropped by the owner of the session and by every invocation */
+	struct kref ref;
 };
 
 /**
@@ -81,6 +99,8 @@ struct qda_dev {
 	struct xarray ctx_xa;
 	/** @ctx_seq: Distinguishes invocations that reuse a context ID */
 	atomic64_t ctx_seq;
+	/** @session_id_seq: Hands out the session IDs the DSP is told */
+	atomic_t session_id_seq;
 	/** @soc_data: Per-SoC DSP integration parameters */
 	const struct qda_soc_data *soc_data;
 	/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
@@ -114,6 +134,7 @@ void qda_unregister_device(struct qda_dev *qdev);
 
 /* Sessions, shared with the legacy shim */
 struct qda_session *qda_session_alloc(struct qda_dev *qdev);
-void qda_session_free(struct qda_session *session);
+void qda_session_get(struct qda_session *session);
+void qda_session_put(struct qda_session *session);
 
 #endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index ec37a38e9215..2971f2f9461f 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -1,9 +1,202 @@
 // SPDX-License-Identifier: GPL-2.0-only
 // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
 #include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
 #include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_print.h>
 #include "qda_drv.h"
 #include "qda_fastrpc.h"
+#include "qda_gem.h"
+
+/*
+ * The caller owns the returned reference and must drop it with
+ * drm_gem_object_put().
+ *
+ * A handle naming a buffer mapped into another session's context bank is
+ * rejected. Looking the handle up in the calling session's table is not
+ * enough on its own: a buffer exported with DRM_IOCTL_PRIME_HANDLE_TO_FD and
+ * imported back on the same device resolves to the original object, still
+ * mapped in the exporting session's bank. The DSP derives the bank from the
+ * address, so forwarding such a buffer would let one session read and write
+ * in another's address space.
+ */
+static int get_gem_obj_from_handle(struct qda_fastrpc_invoke_ctx *ctx, u32 handle,
+				   struct drm_gem_object **gem_obj)
+{
+	struct drm_gem_object *obj;
+
+	if (!ctx->session->file)
+		return -EOPNOTSUPP;
+
+	obj = drm_gem_object_lookup(ctx->session->file, handle);
+	if (!obj)
+		return -ENOENT;
+
+	if (to_qda_gem_obj(obj)->iommu_dev != ctx->session->assigned_iommu_dev) {
+		drm_dbg_driver(&ctx->qdev->drm_dev,
+			       "Buffer %u belongs to another session\n", handle);
+		drm_gem_object_put(obj);
+		return -EACCES;
+	}
+
+	*gem_obj = obj;
+
+	return 0;
+}
+
+static void setup_pages_from_gem_obj(struct qda_gem_obj *qda_gem_obj,
+				     struct qda_phy_page *pages)
+{
+	pages->addr = qda_gem_obj->dma_addr;
+	pages->size = qda_gem_obj->size;
+}
+
+static u64 calculate_page_aligned_size(u64 addr, u64 len)
+{
+	u64 pg_start = (addr & PAGE_MASK) >> PAGE_SHIFT;
+	u64 pg_end = ((addr + len - 1) & PAGE_MASK) >> PAGE_SHIFT;
+
+	return (pg_end - pg_start + 1) * PAGE_SIZE;
+}
+
+/*
+ * Each scalar argument is backed by exactly one contiguous DMA region: native
+ * GEM objects are allocated contiguous and imported ones are rejected at
+ * import time unless they are. So the metadata region needs exactly one
+ * qda_remote_buf, one qda_invoke_buf and one qda_phy_page per scalar; unlike
+ * drivers/misc/fastrpc.c, which supports multi-page buffers and so may need
+ * several qda_phy_page entries per scalar. It ends with the handle list and
+ * the CRC list.
+ */
+static size_t qda_fastrpc_get_meta_size(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	return (sizeof(struct qda_remote_buf) +
+		sizeof(struct qda_invoke_buf) +
+		sizeof(struct qda_phy_page)) * ctx->nscalars +
+		sizeof(u64) * QDA_FASTRPC_MAX_HANDLELIST +
+		sizeof(u32) * QDA_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.
+ *
+ * The list is memory the DSP writes, so its contents are not trusted: it is
+ * bounded by the region qda_fastrpc_get_meta_size() sized for it, ends at the
+ * first zero entry, and each entry is looked up in the calling session's own
+ * handle table, so a bad value can at worst name a buffer that session
+ * already owns.
+ */
+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 < QDA_FASTRPC_MAX_HANDLELIST; i++) {
+		if (!handlelist[i])
+			break;
+		if (ctx->session->file)
+			drm_gem_handle_delete(ctx->session->file, (u32)handlelist[i]);
+	}
+}
+
+/**
+ * struct qda_msg_buf_pool_entry - One idle invoke header buffer
+ */
+struct qda_msg_buf_pool_entry {
+	/** @node: Linkage in struct qda_session.msg_buf_pool */
+	struct list_head node;
+	/** @gem_obj: The buffer, holding the pool's reference on it */
+	struct drm_gem_object *gem_obj;
+};
+
+/*
+ * Hand out one of the session's idle header buffers, so that a workload
+ * invoking the same handle repeatedly does not allocate and tear down a
+ * DMA-coherent, IOMMU-mapped buffer every time. The smallest buffer that
+ * fits is picked, to keep a large one available for a later invocation that
+ * needs it.
+ *
+ * Returns NULL if the pool has nothing big enough, leaving the caller to
+ * allocate.
+ */
+static struct drm_gem_object *qda_session_msg_buf_get(struct qda_session *session, size_t size)
+{
+	struct qda_msg_buf_pool_entry *entry, *best = NULL;
+	struct drm_gem_object *gem_obj;
+
+	guard(mutex)(&session->msg_buf_pool_lock);
+
+	list_for_each_entry(entry, &session->msg_buf_pool, node) {
+		size_t entry_size = to_qda_gem_obj(entry->gem_obj)->size;
+
+		if (entry_size >= size &&
+		    (!best || entry_size < to_qda_gem_obj(best->gem_obj)->size))
+			best = entry;
+	}
+
+	if (!best)
+		return NULL;
+
+	list_del(&best->node);
+	session->msg_buf_pool_count--;
+	gem_obj = best->gem_obj;
+	kfree(best);
+
+	return gem_obj;
+}
+
+/*
+ * Keep a finished header buffer for the session's later invocations, or
+ * release it if the session has reached its share already, so that a session
+ * cannot pin an unbounded number of buffers.
+ */
+static void qda_session_msg_buf_put(struct qda_session *session, struct drm_gem_object *gem_obj)
+{
+	struct qda_msg_buf_pool_entry *entry = NULL;
+
+	scoped_guard(mutex, &session->msg_buf_pool_lock) {
+		if (session->msg_buf_pool_count < QDA_MSG_BUF_POOL_MAX)
+			entry = kzalloc_obj(*entry);
+
+		if (entry) {
+			entry->gem_obj = gem_obj;
+			list_add(&entry->node, &session->msg_buf_pool);
+			session->msg_buf_pool_count++;
+		}
+	}
+
+	if (!entry)
+		drm_gem_object_put(gem_obj);
+}
+
+/**
+ * qda_session_msg_buf_pool_destroy() - Release a session's pooled buffers
+ * @session: Session being torn down
+ *
+ * Runs with the session's last reference gone, so no invocation can return a
+ * buffer to the pool while it is being emptied.
+ */
+void qda_session_msg_buf_pool_destroy(struct qda_session *session)
+{
+	struct qda_msg_buf_pool_entry *entry, *tmp;
+
+	list_for_each_entry_safe(entry, tmp, &session->msg_buf_pool, node) {
+		list_del(&entry->node);
+		drm_gem_object_put(entry->gem_obj);
+		kfree(entry);
+	}
+	session->msg_buf_pool_count = 0;
+}
 
 /**
  * qda_fastrpc_context_free() - Release an invocation
@@ -12,13 +205,33 @@
  * Runs when the last reference goes. The invocation leaves the lookup table
  * here and not earlier, so that its ID cannot be handed to a new invocation
  * while this one can still be found and completed.
+ *
+ * The session is released last, since the buffers released before it were
+ * allocated from its context bank.
  */
 void qda_fastrpc_context_free(struct kref *ref)
 {
 	struct qda_fastrpc_invoke_ctx *ctx =
 		container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);
+	int i;
 
 	xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid));
+
+	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)
+		qda_session_msg_buf_put(ctx->session, &ctx->msg_gem_obj->base);
+
+	if (ctx->session)
+		qda_session_put(ctx->session);
+
+	kfree(ctx->args);
 	kfree(ctx);
 }
 
@@ -70,3 +283,272 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
 
 	return ctx;
 }
+
+/*
+ * Resolve the address of an in/out buffer argument. The buffer is named by a
+ * GEM handle, looked up in the calling session's own handle table, so a
+ * session cannot name another's buffer. The offset and length come from user
+ * space and are checked against the buffer's real size: a region described as
+ * larger than its buffer would let the DSP read and write past the end of it,
+ * into whatever else is mapped in the same context bank.
+ */
+static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
+			      union qda_remote_arg *rpra,
+			      struct qda_phy_page *pages)
+{
+	u64 len = ctx->args[i].length;
+	struct qda_gem_obj *qda_gem_obj;
+	struct drm_gem_object *gem_obj;
+	u64 unaligned_addr;
+	int err;
+
+	if (!ctx->args[i].handle)
+		return -EINVAL;
+
+	err = get_gem_obj_from_handle(ctx, ctx->args[i].handle, &gem_obj);
+	if (err)
+		return err;
+
+	qda_gem_obj = to_qda_gem_obj(gem_obj);
+	if (ctx->args[i].offset > qda_gem_obj->size ||
+	    len > qda_gem_obj->size - ctx->args[i].offset) {
+		drm_dbg_driver(&ctx->qdev->drm_dev,
+			       "Argument %d is out of bounds of its buffer\n", i);
+		drm_gem_object_put(gem_obj);
+		return -EINVAL;
+	}
+
+	ctx->gem_objs[i] = gem_obj;
+
+	/*
+	 * The DSP takes the in-page offset from the low bits of buf.addr; the
+	 * IOVA keeps those bits and stays unique per buffer, as the user VA it
+	 * replaces was.
+	 */
+	unaligned_addr = qda_gem_obj->dma_addr + ctx->args[i].offset;
+	rpra[i].buf.addr = unaligned_addr;
+	pages[i].addr = unaligned_addr & PAGE_MASK;
+	pages[i].size = calculate_page_aligned_size(unaligned_addr, len);
+
+	return 0;
+}
+
+/*
+ * Resolve a DMA-handle argument. The kernel needs the physical page
+ * descriptor, while the DSP identifies the buffer by its GEM handle and
+ * resolves that itself.
+ */
+static int process_dma_handle(struct qda_fastrpc_invoke_ctx *ctx, int i,
+			      union qda_remote_arg *rpra,
+			      struct qda_phy_page *pages)
+{
+	struct drm_gem_object *gem_obj;
+	int err;
+
+	if (!ctx->args[i].handle) {
+		rpra[i].buf.addr = ctx->args[i].offset;
+		rpra[i].buf.len = ctx->args[i].length;
+		return 0;
+	}
+
+	if (ctx->gem_objs[i]) {
+		gem_obj = ctx->gem_objs[i];
+	} else {
+		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].offset;
+
+	return 0;
+}
+
+/**
+ * qda_fastrpc_get_header_size() - Compute the FastRPC message header size
+ * @ctx: FastRPC invocation context
+ *
+ * Two sizes come out of this: the buffer is allocated page-aligned, which is
+ * what the caller gets back, while @ctx->pkt_size keeps the unaligned size
+ * the DSP is told about.
+ *
+ * Return: Page-aligned size of the message payload buffer in bytes
+ */
+static size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	ctx->pkt_size = ALIGN(qda_fastrpc_get_meta_size(ctx), QDA_FASTRPC_ALIGN);
+
+	return PAGE_ALIGN(ctx->pkt_size);
+}
+
+static int qda_fastrpc_get_args(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg)
+{
+	struct qda_invoke_buf *list;
+	struct qda_phy_page *pages;
+	union qda_remote_arg *rpra;
+	int i, err;
+
+	msg->buf = ctx->msg_gem_obj->virt;
+	msg->phys = ctx->msg_gem_obj->dma_addr;
+	/*
+	 * The payload is handed to the DSP whole, including the regions it
+	 * writes into, so clear it rather than rely on how it was allocated.
+	 */
+	memset(msg->buf, 0, ctx->msg_gem_obj->size);
+
+	/* One descriptor of each kind per argument, in three adjacent arrays */
+	rpra = msg->buf;
+	list = (struct qda_invoke_buf *)&rpra[ctx->nscalars];
+	pages = (struct qda_phy_page *)&list[ctx->nscalars];
+	/* Kept so the handle list, which follows the pages, can be found later */
+	ctx->pages = pages;
+
+	for (i = 0; i < ctx->nbufs; ++i) {
+		rpra[i].buf.addr = 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
+ *
+ * Allocates the payload buffer the DSP reads, resolves every argument into
+ * it, and fills in the wire message that points at it.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg)
+{
+	struct qda_dev *qdev = ctx->qdev;
+	struct drm_gem_object *gem_obj;
+	size_t hdr_size;
+	int err;
+
+	hdr_size = qda_fastrpc_get_header_size(ctx);
+
+	gem_obj = qda_session_msg_buf_get(ctx->session, hdr_size);
+	if (!gem_obj) {
+		gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr,
+						hdr_size, ctx->session);
+		if (IS_ERR(gem_obj))
+			return PTR_ERR(gem_obj);
+	}
+
+	ctx->msg_gem_obj = to_qda_gem_obj(gem_obj);
+
+	err = qda_fastrpc_get_args(ctx, msg);
+	if (err)
+		return err;
+
+	/* Ensure the payload is visible to the DSP before the doorbell */
+	dma_wmb();
+
+	msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
+	msg->fastrpc.tid = current->pid;
+	msg->fastrpc.ctx = ctx->ctxid | QDA_USER_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;
+
+	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 = QDA_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
+ * @session: Session issuing the invocation
+ * @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,
+			     struct qda_session *session, void *argp)
+{
+	int err;
+
+	/*
+	 * Held until the invocation is freed, which may be after the file the
+	 * session was opened on is closed: the DSP owns the payload buffer
+	 * until it answers, and that buffer came from the session.
+	 */
+	qda_session_get(session);
+	ctx->session = session;
+
+	err = qda_fastrpc_prepare_args_invoke(ctx, argp);
+	if (err)
+		return err;
+
+	ctx->nscalars = QDA_REMOTE_SCALARS_LENGTH(ctx->sc);
+	ctx->nbufs = QDA_REMOTE_SCALARS_INBUFS(ctx->sc) + QDA_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
index 170b513533b9..8440efc21b77 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -11,8 +11,38 @@
 #include <linux/kref.h>
 #include <linux/types.h>
 #include <linux/workqueue.h>
+#include <drm/qda_accel.h>
 
+struct drm_gem_object;
 struct qda_dev;
+struct qda_gem_obj;
+struct qda_session;
+
+/*
+ * FastRPC scalar extraction macros
+ *
+ * These macros extract different fields from the scalar value that describes
+ * the arguments passed in a FastRPC invocation.
+ */
+#define QDA_REMOTE_SCALARS_INBUFS(sc)		(((sc) >> 16) & 0x0ff)
+#define QDA_REMOTE_SCALARS_OUTBUFS(sc)		(((sc) >> 8) & 0x0ff)
+#define QDA_REMOTE_SCALARS_INHANDLES(sc)	(((sc) >> 4) & 0x0f)
+#define QDA_REMOTE_SCALARS_OUTHANDLES(sc)	((sc) & 0x0f)
+#define QDA_REMOTE_SCALARS_LENGTH(sc)		(QDA_REMOTE_SCALARS_INBUFS(sc) +    \
+						 QDA_REMOTE_SCALARS_OUTBUFS(sc) +   \
+						 QDA_REMOTE_SCALARS_INHANDLES(sc) + \
+						 QDA_REMOTE_SCALARS_OUTHANDLES(sc))
+
+/* FastRPC configuration constants */
+#define QDA_FASTRPC_ALIGN		128	/* Alignment the DSP expects of the payload */
+#define QDA_FASTRPC_MAX_HANDLELIST	16	/* Handles the DSP can ask to release */
+#define QDA_FASTRPC_MAX_CRCLIST		64	/* CRC list entries */
+
+/* Protection domain a session's invocations run in */
+#define QDA_USER_PD			1
+
+/* Idle invoke header buffers a session keeps for reuse */
+#define QDA_MSG_BUF_POOL_MAX		8
 
 /*
  * Layout of the context the DSP echoes back in its response:
@@ -29,6 +59,58 @@ struct qda_dev;
 #define QDA_FASTRPC_CTXID_SEQ_MASK	GENMASK_ULL(63, 16)
 #define QDA_FASTRPC_CTXID_MAX		(QDA_FASTRPC_CTXID_MASK >> 8)
 
+/**
+ * struct qda_remote_dmahandle - Remote DMA handle descriptor
+ */
+struct qda_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 qda_remote_buf - Remote buffer descriptor
+ */
+struct qda_remote_buf {
+	/** @addr: Address of the argument as the DSP sees it */
+	u64 addr;
+	/** @len: Length of the buffer in bytes */
+	u64 len;
+};
+
+/**
+ * union qda_remote_arg - Remote argument (buffer or DMA handle)
+ */
+union qda_remote_arg {
+	/** @buf: Buffer descriptor */
+	struct qda_remote_buf buf;
+	/** @dma: DMA handle descriptor */
+	struct qda_remote_dmahandle dma;
+};
+
+/**
+ * struct qda_phy_page - Physical page descriptor
+ */
+struct qda_phy_page {
+	/** @addr: Physical (IOMMU) address of the region */
+	u64 addr;
+	/** @size: Size of the contiguous region in bytes */
+	u64 size;
+};
+
+/**
+ * struct qda_invoke_buf - Invoke buffer descriptor
+ */
+struct qda_invoke_buf {
+	/** @num: Number of contiguous physical regions */
+	u32 num;
+	/** @pgidx: Index into the physical page array */
+	u32 pgidx;
+};
+
 /**
  * struct qda_fastrpc_msg - FastRPC wire message for remote invocations
  *
@@ -62,6 +144,10 @@ struct qda_msg {
 	 * Must be the first member.
 	 */
 	struct qda_fastrpc_msg fastrpc;
+	/** @buf: Kernel virtual address of the payload buffer */
+	void *buf;
+	/** @phys: Physical/DMA address of the payload buffer */
+	u64 phys;
 	/** @fastrpc_ctx: Back-pointer to the owning invocation context */
 	struct qda_fastrpc_invoke_ctx *fastrpc_ctx;
 };
@@ -72,10 +158,30 @@ struct qda_msg {
 struct qda_fastrpc_invoke_ctx {
 	/** @qdev: Device the invocation was issued on */
 	struct qda_dev *qdev;
+	/** @session: Session that initiated this invocation */
+	struct qda_session *session;
 	/** @ctxid: Wire context with the protection domain bits clear */
 	u64 ctxid;
 	/** @retval: Status the DSP reported, or an error if it never answered */
 	int retval;
+	/** @nscalars: Total number of scalar arguments */
+	int nscalars;
+	/** @nbufs: Number of buffer arguments; the rest are handle arguments */
+	int nbufs;
+	/** @handle: Handle of the remote method being invoked */
+	u32 handle;
+	/** @sc: Scalars value encoding in/out buffer counts */
+	u32 sc;
+	/** @pkt_size: Total payload size in bytes */
+	size_t pkt_size;
+	/** @args: Invoke argument descriptors */
+	struct drm_qda_fastrpc_invoke_args *args;
+	/** @gem_objs: Buffer each argument resolved to, held until the end */
+	struct drm_gem_object **gem_objs;
+	/** @msg_gem_obj: GEM object backing the message payload buffer */
+	struct qda_gem_obj *msg_gem_obj;
+	/** @pages: Array of physical page descriptors inside the payload */
+	struct qda_phy_page *pages;
 	/** @work: Completed once the DSP has answered */
 	struct completion work;
 	/**
@@ -94,5 +200,10 @@ struct qda_fastrpc_invoke_ctx {
 
 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);
+void qda_session_msg_buf_pool_destroy(struct qda_session *session);
+int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
+			     struct qda_session *session, void *argp);
+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_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index 3566a10c6488..9639ea84b200 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -33,7 +33,7 @@ static void legacy_session_destroy(struct qda_session *session)
 {
 	struct drm_device *drm_dev = &session->qda_dev->drm_dev;
 
-	qda_session_free(session);
+	qda_session_put(session);
 	drm_dev_put(drm_dev);
 }
 
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 8e64480cdd82..6d81f0ef47b6 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -70,6 +70,42 @@ struct drm_qda_gem_mmap_offset {
 	__u32 pad;
 };
 
+/**
+ * struct drm_qda_fastrpc_invoke_args - FastRPC invocation argument descriptor
+ * @offset: Byte offset within the buffer at which the argument starts
+ * @length: Length of the argument data in bytes
+ * @handle: GEM handle of the buffer holding the argument
+ * @attr: Argument attributes and flags
+ *
+ * Describes a single argument of a FastRPC invocation. Every argument lives
+ * in a buffer, named by the handle it was created or imported under, and
+ * @offset and @length must describe a region within that buffer. Userspace
+ * imports any DMA-BUF fd to a GEM handle before passing it here; the driver
+ * never accepts DMA-BUF fds directly.
+ */
+struct drm_qda_fastrpc_invoke_args {
+	__u64 offset;
+	__u64 length;
+	__u32 handle;
+	__u32 attr;
+};
+
+/**
+ * struct drm_qda_invoke_args - Dynamic FastRPC invocation parameters
+ * @handle: Remote handle to invoke on the DSP
+ * @sc: Scalars value encoding the remote method and the argument counts
+ * @args: User-space pointer to an array of struct
+ *        drm_qda_fastrpc_invoke_args entries, one per argument @sc describes
+ *
+ * Runs a method on the DSP. Results are written directly into the argument
+ * buffers, so there is nothing to read back from this structure.
+ */
+struct drm_qda_invoke_args {
+	__u32 handle;
+	__u32 sc;
+	__u64 args;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (11 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:41   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim Ekansh Gupta
                   ` (12 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Tie the invocation pieces together behind an ioctl: allocate an
invocation, take its arguments from user space, build the payload, send
it to the DSP, wait, and act on the answer.

The wait is interruptible, so a DSP that never answers does not leave
the caller unkillable. An interrupted caller drops its own reference and
returns, but the invocation lives on, because the DSP still owns the
payload buffer and may write into it. It is freed once the DSP answers
or the transport goes away.

When the remote processor goes away, invocations may be waiting for
answers that will never come. Those waiters are failed with -EPIPE
and woken so they can release the buffers they hold. The flush runs
after the device is unplugged, so the response handler is drained and
the flush is the only thing completing invocations.

The flush drops the reference the send path took for the response. An
invocation that has not been sent yet, or whose response has already
been handled, does not hold that reference, so the flush can drop one
that is not its own. Both windows are narrow but open exactly when the
DSP crashes.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |  1 +
 drivers/accel/qda/qda_fastrpc.c | 67 +++++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h |  2 ++
 drivers/accel/qda/qda_ioctl.c   | 36 ++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.h   |  1 +
 drivers/accel/qda/qda_rpmsg.c   |  1 +
 include/uapi/drm/qda_accel.h    |  3 ++
 7 files changed, 111 insertions(+)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 99bd37fca438..7d44342d3f67 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -110,6 +110,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_QUERY, qda_ioctl_query, 0),
 	DRM_IOCTL_DEF_DRV(QDA_GEM_CREATE, qda_ioctl_gem_create, 0),
 	DRM_IOCTL_DEF_DRV(QDA_GEM_MMAP_OFFSET, qda_ioctl_gem_mmap_offset, 0),
+	DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 2971f2f9461f..3a1959e2c53e 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -10,6 +10,7 @@
 #include "qda_drv.h"
 #include "qda_fastrpc.h"
 #include "qda_gem.h"
+#include "qda_rpmsg.h"
 
 /*
  * The caller owns the returned reference and must drop it with
@@ -484,6 +485,72 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
 	return 0;
 }
 
+/**
+ * qda_fastrpc_flush_pending() - Fail every invocation still in flight
+ * @qdev: Device whose invocations should be failed
+ *
+ * The DSP has gone away and will never answer, so stand in for the response
+ * handler: wake each waiter with an error and drop the reference the send
+ * path took for that handler. Each waiter then tears its invocation down,
+ * releasing the buffers it holds.
+ *
+ * Must run after drm_dev_unplug() has drained the response handler, so this
+ * is the only path completing an invocation.
+ */
+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_invoke_common() - Shared tail of a FastRPC invocation
+ * @qdev: Device to invoke on
+ * @ctx: Invocation whose arguments are already prepared
+ *
+ * Packs the invocation into a message, sends it, waits for the DSP, and acts
+ * on the answer. Common to the accel UAPI and the legacy shim, once each has
+ * prepared ctx->args and ctx->gem_objs by its own means.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ctx *ctx)
+{
+	struct qda_msg msg = {};
+	int err;
+
+	err = qda_fastrpc_invoke_pack(ctx, &msg);
+	if (err)
+		return err;
+
+	err = qda_rpmsg_send_msg(qdev, &msg);
+	if (err)
+		return err;
+
+	err = qda_rpmsg_wait_for_rsp(ctx);
+	if (err)
+		return err;
+
+	/* Ensure the results written by the DSP are visible before reading */
+	dma_rmb();
+
+	err = ctx->retval;
+	if (err) {
+		drm_dbg_driver(&qdev->drm_dev, "DSP returned status %d\n", ctx->retval);
+		return err;
+	}
+
+	qda_fastrpc_cleanup_handlelist(ctx);
+
+	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.
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 8440efc21b77..61f80c64e6d2 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -205,5 +205,7 @@ void qda_session_msg_buf_pool_destroy(struct qda_session *session);
 int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 			     struct qda_session *session, void *argp);
 int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg);
+int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ctx *ctx);
+void qda_fastrpc_flush_pending(struct qda_dev *qdev);
 
 #endif /* __QDA_FASTRPC_H__ */
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index b11743ab791e..e9f4151e344a 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -6,6 +6,7 @@
 #include <drm/drm_gem.h>
 #include <drm/qda_accel.h>
 #include "qda_drv.h"
+#include "qda_fastrpc.h"
 #include "qda_gem.h"
 #include "qda_ioctl.h"
 
@@ -73,3 +74,38 @@ 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(void *data, struct qda_session *session)
+{
+	struct qda_dev *qdev = session->qda_dev;
+	struct qda_fastrpc_invoke_ctx *ctx;
+	int err;
+
+	ctx = qda_fastrpc_context_alloc(qdev);
+	if (IS_ERR(ctx))
+		return PTR_ERR(ctx);
+
+	err = qda_fastrpc_prepare_args(ctx, session, data);
+	if (err)
+		goto out_put;
+
+	err = qda_fastrpc_invoke_common(qdev, ctx);
+
+out_put:
+	kref_put(&ctx->refcount, qda_fastrpc_context_free);
+
+	return err;
+}
+
+/**
+ * qda_ioctl_invoke() - Run a method on the DSP
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_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(data, file_priv->driver_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 43b56ab3f012..7e58df3eb2e1 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -160,6 +160,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/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 6d81f0ef47b6..c21bc5b2446c 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -15,6 +15,7 @@ extern "C" {
 #define DRM_QDA_QUERY			0x00
 #define DRM_QDA_GEM_CREATE		0x01
 #define DRM_QDA_GEM_MMAP_OFFSET		0x02
+#define DRM_QDA_REMOTE_INVOKE		0x03
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -22,6 +23,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

-- 
2.34.1


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

* [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (12 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:47   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 15/25] accel/qda: Add DSP process creation and release Ekansh Gupta
                   ` (11 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

Let applications written for the /dev/fastrpc-* interface run methods
on the DSP, by converting their arguments into what the invocation code
takes and handing them over.

A legacy argument is a user pointer, a length and a file descriptor for
the buffer it lives in. The shim imports each descriptor into the
session's context bank, so the DSP can reach the buffer, and passes the
resulting objects along with the pointers and lengths. A session the
shim created has no handle table, so these arguments cannot be named by
handle the way the accel interface does; the invocation takes the
already-resolved objects instead. Ownership of each object moves into
the invocation, so the shim drops its references itself only if
importing a later argument fails.

The pointer a legacy caller passes is an address in its own mapping of
the buffer, and the interface predates GEM handles and cannot change, so
the offset into the buffer is recovered from that mapping. Unlike
handle-named arguments, these are not checked against the size of the
buffer they name.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/qda_fastrpc.c      | 151 +++++++++++++++++++++++++++++++++--
 drivers/accel/qda/qda_fastrpc.h      |  19 +++++
 drivers/accel/qda/qda_fastrpc_shim.c |  87 ++++++++++++++++++++
 drivers/accel/qda/qda_legacy_ops.c   |   8 ++
 drivers/accel/qda/qda_legacy_ops.h   |   4 +
 5 files changed, 262 insertions(+), 7 deletions(-)

diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 3a1959e2c53e..693dd6f035d2 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -55,6 +55,22 @@ static void setup_pages_from_gem_obj(struct qda_gem_obj *qda_gem_obj,
 	pages->size = qda_gem_obj->size;
 }
 
+static int calculate_vma_offset(u64 user_ptr, u64 *offset)
+{
+	struct vm_area_struct *vma;
+	int ret = -EFAULT;
+
+	mmap_read_lock(current->mm);
+	vma = find_vma(current->mm, user_ptr);
+	if (vma && vma->vm_start <= user_ptr) {
+		*offset = (user_ptr & PAGE_MASK) - vma->vm_start;
+		ret = 0;
+	}
+	mmap_read_unlock(current->mm);
+
+	return ret;
+}
+
 static u64 calculate_page_aligned_size(u64 addr, u64 len)
 {
 	u64 pg_start = (addr & PAGE_MASK) >> PAGE_SHIFT;
@@ -286,23 +302,45 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
 }
 
 /*
- * Resolve the address of an in/out buffer argument. The buffer is named by a
- * GEM handle, looked up in the calling session's own handle table, so a
- * session cannot name another's buffer. The offset and length come from user
- * space and are checked against the buffer's real size: a region described as
- * larger than its buffer would let the DSP read and write past the end of it,
- * into whatever else is mapped in the same context bank.
+ * Resolve the address of an in/out buffer argument. The buffer is either a
+ * GEM object the legacy shim already resolved into ctx->gem_objs[i], or a GEM
+ * handle looked up in the calling session's own handle table. For a handle,
+ * the offset and length come from user space and are checked against the
+ * buffer's real size: a region described as larger than its buffer would let
+ * the DSP read and write past the end of it, into whatever else is mapped in
+ * the same context bank.
  */
 static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
 			      union qda_remote_arg *rpra,
 			      struct qda_phy_page *pages)
 {
 	u64 len = ctx->args[i].length;
-	struct qda_gem_obj *qda_gem_obj;
 	struct drm_gem_object *gem_obj;
+	struct qda_gem_obj *qda_gem_obj;
 	u64 unaligned_addr;
+	u64 vma_offset = 0;
 	int err;
 
+	if (ctx->gem_objs[i]) {
+		/*
+		 * The legacy ABI predates GEM handles, so the offset field
+		 * holds an address in the caller's own mapping of the buffer,
+		 * or 0 if it has none, meaning the start of the buffer.
+		 */
+		qda_gem_obj = to_qda_gem_obj(ctx->gem_objs[i]);
+		if (ctx->args[i].offset) {
+			err = calculate_vma_offset(ctx->args[i].offset, &vma_offset);
+			if (err)
+				return err;
+		}
+
+		rpra[i].buf.addr = ctx->args[i].offset;
+		pages[i].addr = qda_gem_obj->dma_addr + vma_offset;
+		pages[i].size = calculate_page_aligned_size(ctx->args[i].offset, len);
+
+		return 0;
+	}
+
 	if (!ctx->args[i].handle)
 		return -EINVAL;
 
@@ -551,6 +589,105 @@ int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ct
 	return 0;
 }
 
+/**
+ * qda_fastrpc_invoke_legacy() - Perform a dynamic invocation on behalf of the legacy shim
+ * @qdev: Device to invoke on
+ * @session: Legacy session (no backing drm_file)
+ * @handle: Remote handle to invoke on the DSP
+ * @sc: FastRPC scalars value encoding the number of in/out buffers
+ * @legacy_args: Kernel-side argument array, one entry per QDA_REMOTE_SCALARS_LENGTH(sc)
+ *
+ * Unlike the accel UAPI path, the argument descriptors and any backing GEM
+ * objects already live in kernel memory: the shim has already resolved
+ * every fd-backed argument via its own import path, since a legacy session
+ * has no GEM handle table to resolve a handle-based argument against.
+ * Ownership of each @legacy_args[i].obj reference transfers to this function,
+ * on failure as well as on success.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
+			      u32 handle, u32 sc, struct qda_legacy_invoke_arg *legacy_args)
+{
+	struct qda_fastrpc_invoke_ctx *ctx;
+	struct drm_qda_fastrpc_invoke_args *args;
+	u32 nscalars;
+	int err;
+	int i;
+
+	nscalars = QDA_REMOTE_SCALARS_LENGTH(sc);
+
+	/*
+	 * An fd naming a buffer another session exported resolves to the
+	 * original object, still mapped in that session's context bank, so
+	 * the same check as for a handle applies.
+	 */
+	for (i = 0; i < nscalars; ++i) {
+		if (legacy_args[i].obj &&
+		    to_qda_gem_obj(legacy_args[i].obj)->iommu_dev != session->assigned_iommu_dev) {
+			err = -EACCES;
+			goto err_put_legacy_objs;
+		}
+	}
+
+	ctx = qda_fastrpc_context_alloc(qdev);
+	if (IS_ERR(ctx)) {
+		err = PTR_ERR(ctx);
+		goto err_put_legacy_objs;
+	}
+
+	/* Dropped by qda_fastrpc_context_free(), as for the accel UAPI path */
+	qda_session_get(session);
+	ctx->session = session;
+	ctx->handle = handle;
+	ctx->sc = sc;
+
+	ctx->nbufs = QDA_REMOTE_SCALARS_INBUFS(sc) + QDA_REMOTE_SCALARS_OUTBUFS(sc);
+	if (nscalars) {
+		args = kzalloc_objs(*args, nscalars);
+		if (!args) {
+			err = -ENOMEM;
+			goto err_put_legacy_objs_ctx;
+		}
+
+		ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
+		if (!ctx->gem_objs) {
+			kfree(args);
+			err = -ENOMEM;
+			goto err_put_legacy_objs_ctx;
+		}
+
+		for (i = 0; i < nscalars; ++i) {
+			args[i].offset = legacy_args[i].ptr;
+			args[i].length = legacy_args[i].length;
+			/* process_dma_handle() skips handle args whose handle is 0 */
+			args[i].handle = (i >= ctx->nbufs && legacy_args[i].obj) ?
+					 (u32)legacy_args[i].fd : 0;
+			ctx->gem_objs[i] = legacy_args[i].obj;
+		}
+
+		ctx->args = args;
+	}
+
+	ctx->nscalars = nscalars;
+
+	err = qda_fastrpc_invoke_common(qdev, ctx);
+
+	kref_put(&ctx->refcount, qda_fastrpc_context_free);
+
+	return err;
+
+err_put_legacy_objs_ctx:
+	kref_put(&ctx->refcount, qda_fastrpc_context_free);
+err_put_legacy_objs:
+	/* The context never took the objects, so release them here */
+	for (i = 0; i < nscalars; ++i)
+		if (legacy_args[i].obj)
+			drm_gem_object_put(legacy_args[i].obj);
+
+	return err;
+}
+
 /*
  * INVOKE_DYNAMIC: the argument descriptors are supplied by user space, which
  * is also responsible for having imported every buffer to a GEM handle.
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 61f80c64e6d2..3243bd1065c6 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -198,6 +198,23 @@ struct qda_fastrpc_invoke_ctx {
 	struct work_struct put_work;
 };
 
+/**
+ * struct qda_legacy_invoke_arg - Kernel-side invoke argument for the legacy shim
+ *
+ * Built by the FastRPC compatibility shim from a legacy fastrpc_invoke_args
+ * entry; @obj is NULL for an argument with no backing fd.
+ */
+struct qda_legacy_invoke_arg {
+	/** @ptr: Argument pointer (user virtual address) */
+	u64 ptr;
+	/** @length: Length of the argument data in bytes */
+	u64 length;
+	/** @obj: Pre-resolved GEM object for an fd-backed argument, or NULL */
+	struct drm_gem_object *obj;
+	/** @fd: Legacy fd, forwarded as the DSP-visible handle for handle arguments */
+	s32 fd;
+};
+
 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);
@@ -207,5 +224,7 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg);
 int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ctx *ctx);
 void qda_fastrpc_flush_pending(struct qda_dev *qdev);
+int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
+			      u32 handle, u32 sc, struct qda_legacy_invoke_arg *legacy_args);
 
 #endif /* __QDA_FASTRPC_H__ */
diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
index 25f5c242efde..03fce990ccdb 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -15,8 +15,10 @@
 #include <linux/string.h>
 #include <linux/uaccess.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_gem.h>
 #include <uapi/misc/fastrpc.h>
 #include "qda_drv.h"
+#include "qda_fastrpc.h"
 #include "qda_legacy_ops.h"
 
 /* State of one open /dev/fastrpc-* file */
@@ -50,6 +52,25 @@ struct qda_shim_dev {
 
 static const struct file_operations qda_shim_fops;
 
+/* A negative @fd means the argument has no buffer, which is not an error */
+static struct drm_gem_object *shim_import_fd(struct qda_session *session, s32 fd)
+{
+	struct drm_gem_object *obj;
+	struct dma_buf *dmabuf;
+
+	if (fd < 0)
+		return NULL;
+
+	dmabuf = dma_buf_get(fd);
+	if (IS_ERR(dmabuf))
+		return ERR_CAST(dmabuf);
+
+	obj = qda_legacy_ops_table.import_dmabuf(session, dmabuf);
+	dma_buf_put(dmabuf);
+
+	return obj;
+}
+
 static int qda_shim_open(struct inode *inode, struct file *file)
 {
 	struct qda_shim_node *snode = container_of(file->private_data, struct qda_shim_node,
@@ -120,11 +141,77 @@ static long qda_shim_alloc_dma_buf(struct qda_shim_file *sf, void __user *argp)
 	return 0;
 }
 
+static long qda_shim_invoke(struct qda_shim_file *sf, void __user *argp)
+{
+	struct qda_legacy_invoke_arg *largs;
+	struct fastrpc_invoke_args *uargs;
+	struct fastrpc_invoke args;
+	u32 nargs;
+	int err;
+	int i;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	nargs = QDA_REMOTE_SCALARS_LENGTH(args.sc);
+	if (!nargs)
+		return qda_legacy_ops_table.invoke(sf->session, args.handle, args.sc, NULL);
+
+	uargs = kcalloc(nargs, sizeof(*uargs), GFP_KERNEL);
+	if (!uargs)
+		return -ENOMEM;
+
+	if (copy_from_user(uargs, u64_to_user_ptr(args.args), nargs * sizeof(*uargs))) {
+		kfree(uargs);
+		return -EFAULT;
+	}
+
+	largs = kcalloc(nargs, sizeof(*largs), GFP_KERNEL);
+	if (!largs) {
+		kfree(uargs);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < nargs; ++i) {
+		struct drm_gem_object *obj;
+
+		largs[i].ptr = uargs[i].ptr;
+		largs[i].length = uargs[i].length;
+		largs[i].fd = uargs[i].fd;
+
+		obj = shim_import_fd(sf->session, uargs[i].fd);
+		if (IS_ERR(obj)) {
+			err = PTR_ERR(obj);
+			goto err_put_objs;
+		}
+		largs[i].obj = obj;
+	}
+
+	kfree(uargs);
+
+	/* Takes ownership of every largs[i].obj, whether or not it succeeds */
+	err = qda_legacy_ops_table.invoke(sf->session, args.handle, args.sc, largs);
+	kfree(largs);
+
+	return err;
+
+err_put_objs:
+	while (i-- > 0)
+		if (largs[i].obj)
+			drm_gem_object_put(largs[i].obj);
+	kfree(largs);
+	kfree(uargs);
+
+	return err;
+}
+
 static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void __user *argp)
 {
 	switch (cmd) {
 	case FASTRPC_IOCTL_ALLOC_DMA_BUFF:
 		return qda_shim_alloc_dma_buf(sf, argp);
+	case FASTRPC_IOCTL_INVOKE:
+		return qda_shim_invoke(sf, argp);
 	default:
 		/*
 		 * Includes FASTRPC_IOCTL_FREE_DMA_BUFF, which the driver this
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index 9639ea84b200..74ee4995350e 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -8,6 +8,7 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_prime.h>
 #include "qda_drv.h"
+#include "qda_fastrpc.h"
 #include "qda_gem.h"
 #include "qda_legacy_ops.h"
 #include "qda_prime.h"
@@ -71,10 +72,17 @@ static struct drm_gem_object *legacy_import_dmabuf(struct qda_session *session,
 	return qda_prime_import_dmabuf(&session->qda_dev->drm_dev, session, dmabuf);
 }
 
+static int legacy_invoke(struct qda_session *session, u32 handle, u32 sc,
+			 struct qda_legacy_invoke_arg *args)
+{
+	return qda_fastrpc_invoke_legacy(session->qda_dev, session, handle, sc, args);
+}
+
 const struct qda_legacy_ops qda_legacy_ops_table = {
 	.session_create = legacy_session_create,
 	.session_destroy = legacy_session_destroy,
 	.alloc_dmabuf = legacy_alloc_dmabuf,
 	.import_dmabuf = legacy_import_dmabuf,
+	.invoke = legacy_invoke,
 };
 EXPORT_SYMBOL_FOR_MODULES(qda_legacy_ops_table, "qda_fastrpc_shim");
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
index 76b80dc87a4b..ba1ce3c0a22b 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -11,6 +11,7 @@
 struct dma_buf;
 struct drm_gem_object;
 struct qda_dev;
+struct qda_legacy_invoke_arg;
 struct qda_session;
 
 /**
@@ -34,6 +35,9 @@ struct qda_legacy_ops {
 	/** @import_dmabuf: Map a DMA-BUF into a session's context bank */
 	struct drm_gem_object *(*import_dmabuf)(struct qda_session *session,
 						struct dma_buf *dmabuf);
+	/** @invoke: Run a method on the DSP; takes ownership of each @args[i].obj */
+	int (*invoke)(struct qda_session *session, u32 handle, u32 sc,
+		      struct qda_legacy_invoke_arg *args);
 };
 
 extern const struct qda_legacy_ops qda_legacy_ops_table;

-- 
2.34.1


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

* [PATCH v4 15/25] accel/qda: Add DSP process creation and release
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (13 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:50   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim Ekansh Gupta
                   ` (10 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

An application needs a process on the DSP to run its own code, and
that process has to be torn down when the application goes away. Add
the requests that create and release one, and the ioctl that creates
it.

Creating a process hands the DSP an ELF image, which the caller names
by buffer handle, together with a block of memory the DSP loads the
image into. That memory is allocated per session and kept until the
process is released, since the DSP uses it for as long as the process
runs. A process created again on the same session replaces the first,
so the memory held for the previous one is dropped first.

A signed process runs with the DSP's trust in the signature, so
creating one requires CAP_SYS_ADMIN; only an unsigned process may be
created without it.

Releasing is sent when a session closes, if the device is still there
to receive it. Once the DSP has torn the process down, the memory held
for it is freed.

Every request after a successful create is directed at the new
protection domain, so the session remembers which one it is in and the
message built for a request carries it. Requests addressed to the
initialisation handle carry session ID 0, since the session has no
process yet to identify.

Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |  16 +++-
 drivers/accel/qda/qda_drv.h     |  11 +++
 drivers/accel/qda/qda_fastrpc.c | 202 ++++++++++++++++++++++++++++++++++++++--
 drivers/accel/qda/qda_fastrpc.h |  48 +++++++++-
 drivers/accel/qda/qda_ioctl.c   |  59 +++++++++++-
 drivers/accel/qda/qda_ioctl.h   |   1 +
 include/uapi/drm/qda_accel.h    |  28 ++++++
 7 files changed, 354 insertions(+), 11 deletions(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 7d44342d3f67..983c6a3dcfb4 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -13,6 +13,7 @@
 
 #include "qda_drv.h"
 #include "qda_fastrpc.h"
+#include "qda_gem.h"
 #include "qda_ioctl.h"
 #include "qda_prime.h"
 
@@ -72,6 +73,9 @@ static void qda_session_release(struct kref *ref)
 
 	qda_session_msg_buf_pool_destroy(session);
 
+	if (session->init_mem_gem_obj)
+		drm_gem_object_put(&session->init_mem_gem_obj->base);
+
 	if (session->assigned_iommu_dev)
 		qda_memory_manager_release_device(session->assigned_iommu_dev);
 
@@ -101,7 +105,16 @@ EXPORT_SYMBOL_FOR_MODULES(qda_session_put, "qda_fastrpc_shim");
 
 static void qda_postclose(struct drm_device *dev, struct drm_file *file)
 {
-	qda_session_put(file->driver_priv);
+	struct qda_session *session = file->driver_priv;
+	int idx;
+
+	/* Only a session that created a process has one to release */
+	if (READ_ONCE(session->init_mem_gem_obj) && drm_dev_enter(dev, &idx)) {
+		qda_release_dsp_process(session->qda_dev, session);
+		drm_dev_exit(idx);
+	}
+
+	qda_session_put(session);
 }
 
 DEFINE_DRM_ACCEL_FOPS(qda_accel_fops);
@@ -111,6 +124,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_GEM_CREATE, qda_ioctl_gem_create, 0),
 	DRM_IOCTL_DEF_DRV(QDA_GEM_MMAP_OFFSET, qda_ioctl_gem_mmap_offset, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
+	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_CREATE, qda_ioctl_init_create, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 80a8569b311a..2f98fd83fdda 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -56,6 +56,14 @@ struct qda_session {
 	struct qda_iommu_device *assigned_iommu_dev;
 	/** @remote_session_id: Identifies this session to the DSP */
 	int remote_session_id;
+	/** @pd: Protection domain this session's requests are directed at */
+	int pd;
+	/**
+	 * @init_mem_gem_obj: Memory the DSP loaded this session's process
+	 * into, held for as long as the process runs. Swapped with xchg(), as
+	 * ioctls on one file can run concurrently.
+	 */
+	struct qda_gem_obj *init_mem_gem_obj;
 	/**
 	 * @msg_buf_pool: Invoke header buffers this session is done with,
 	 * kept for its later invocations to reuse.
@@ -137,4 +145,7 @@ struct qda_session *qda_session_alloc(struct qda_dev *qdev);
 void qda_session_get(struct qda_session *session);
 void qda_session_put(struct qda_session *session);
 
+/* DSP process management */
+int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session);
+
 #endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 693dd6f035d2..4672405ea4b7 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -1,9 +1,12 @@
 // SPDX-License-Identifier: GPL-2.0-only
 // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
 #include <linux/bitfield.h>
+#include <linux/capability.h>
 #include <linux/dma-mapping.h>
 #include <linux/mm.h>
+#include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include <linux/uaccess.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_print.h>
@@ -245,6 +248,9 @@ void qda_fastrpc_context_free(struct kref *ref)
 	if (ctx->msg_gem_obj)
 		qda_session_msg_buf_put(ctx->session, &ctx->msg_gem_obj->base);
 
+	if (ctx->init_mem_gem_obj)
+		drm_gem_object_put(&ctx->init_mem_gem_obj->base);
+
 	if (ctx->session)
 		qda_session_put(ctx->session);
 
@@ -301,10 +307,54 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
 	return ctx;
 }
 
+/*
+ * Allocate one DMA-coherent object to hold every buffer the driver builds for
+ * one of its own requests, so they cost a single page rather than one each.
+ * The memory is cleared, so output slots start out clean.
+ */
+static struct qda_gem_obj *alloc_kernel_gem(struct qda_fastrpc_invoke_ctx *ctx, size_t size)
+{
+	struct qda_dev *qdev = ctx->qdev;
+	struct drm_gem_object *gem_obj;
+
+	gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr, size, ctx->session);
+	if (IS_ERR(gem_obj))
+		return ERR_CAST(gem_obj);
+
+	memset(to_qda_gem_obj(gem_obj)->virt, 0, to_qda_gem_obj(gem_obj)->size);
+
+	return to_qda_gem_obj(gem_obj);
+}
+
+/* The context releases whatever has been allocated here if a later step fails */
+static int alloc_kernel_args(struct qda_fastrpc_invoke_ctx *ctx, u32 nargs)
+{
+	ctx->args = kzalloc_objs(*ctx->args, nargs);
+	if (!ctx->args)
+		return -ENOMEM;
+
+	ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nargs);
+	if (!ctx->gem_objs)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/* Describe the region at @ptr in @kernel_gem as argument @idx, taking a reference on it */
+static void assign_kernel_gem_arg(struct qda_fastrpc_invoke_ctx *ctx, int idx,
+				  struct qda_gem_obj *kernel_gem, void *ptr, size_t len)
+{
+	ctx->args[idx].offset = (u8 *)ptr - (u8 *)kernel_gem->virt;
+	ctx->args[idx].length = len;
+	drm_gem_object_get(&kernel_gem->base);
+	ctx->gem_objs[idx] = &kernel_gem->base;
+}
+
 /*
  * Resolve the address of an in/out buffer argument. The buffer is either a
- * GEM object the legacy shim already resolved into ctx->gem_objs[i], or a GEM
- * handle looked up in the calling session's own handle table. For a handle,
+ * GEM object already resolved into ctx->gem_objs[i], by the legacy shim or by
+ * the driver for its own requests, or a GEM handle looked up in the calling
+ * session's own handle table. For a handle,
  * the offset and length come from user space and are checked against the
  * buffer's real size: a region described as larger than its buffer would let
  * the DSP read and write past the end of it, into whatever else is mapped in
@@ -321,7 +371,7 @@ static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
 	u64 vma_offset = 0;
 	int err;
 
-	if (ctx->gem_objs[i]) {
+	if (ctx->gem_objs[i] && ctx->legacy) {
 		/*
 		 * The legacy ABI predates GEM handles, so the offset field
 		 * holds an address in the caller's own mapping of the buffer,
@@ -341,6 +391,17 @@ static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
 		return 0;
 	}
 
+	if (ctx->gem_objs[i]) {
+		/* Built by the driver, so the offset is a byte offset into the buffer */
+		qda_gem_obj = to_qda_gem_obj(ctx->gem_objs[i]);
+		unaligned_addr = qda_gem_obj->dma_addr + ctx->args[i].offset;
+		rpra[i].buf.addr = unaligned_addr;
+		pages[i].addr = unaligned_addr & PAGE_MASK;
+		pages[i].size = calculate_page_aligned_size(unaligned_addr, len);
+
+		return 0;
+	}
+
 	if (!ctx->args[i].handle)
 		return -EINVAL;
 
@@ -511,9 +572,14 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
 	/* Ensure the payload is visible to the DSP before the doorbell */
 	dma_wmb();
 
-	msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
+	/* The session has no process yet to identify while one is being created */
+	if (ctx->handle == QDA_FASTRPC_INIT_HANDLE)
+		msg->fastrpc.remote_session_id = 0;
+	else
+		msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
+
 	msg->fastrpc.tid = current->pid;
-	msg->fastrpc.ctx = ctx->ctxid | QDA_USER_PD;
+	msg->fastrpc.ctx = ctx->ctxid | ctx->pd;
 	msg->fastrpc.handle = ctx->handle;
 	msg->fastrpc.sc = ctx->sc;
 	msg->fastrpc.addr = msg->phys;
@@ -639,6 +705,9 @@ int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
 	/* Dropped by qda_fastrpc_context_free(), as for the accel UAPI path */
 	qda_session_get(session);
 	ctx->session = session;
+	ctx->type = QDA_FASTRPC_RMID_INVOKE_DYNAMIC;
+	ctx->legacy = true;
+	ctx->pd = READ_ONCE(session->pd);
 	ctx->handle = handle;
 	ctx->sc = sc;
 
@@ -688,6 +757,110 @@ int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
 	return err;
 }
 
+/*
+ * INIT_RELEASE packed layout:
+ *   [0] remote_session_id	sizeof(int)	IN
+ */
+static int qda_fastrpc_prepare_args_release_process(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	struct qda_gem_obj *kernel_gem;
+	int err;
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_RELEASE, 1, 0);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 1);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, sizeof(int));
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+
+	*(int *)kernel_gem->virt = ctx->session->remote_session_id;
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, kernel_gem->virt, sizeof(int));
+	drm_gem_object_put(&kernel_gem->base);
+
+	return 0;
+}
+
+/*
+ * INIT_CREATE / INIT_CREATE_ATTR packed layout:
+ *   [0] inbuf	sizeof(qda_fastrpc_create_process_inbuf)	IN
+ *   [1] comm	TASK_COMM_LEN					IN
+ *   [2] ELF	buffer named by the caller's handle, not packed	IN
+ *   [3] pages	sizeof(qda_phy_page), the process image memory	IN
+ *
+ * The attributes and signature length travel in @inbuf. The _ATTR method is
+ * used when any attribute is set, as in drivers/misc/fastrpc.c.
+ */
+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_fastrpc_create_process_inbuf *inbuf;
+	struct qda_dev *qdev = ctx->qdev;
+	struct drm_gem_object *gem_obj;
+	struct qda_gem_obj *kernel_gem;
+	u8 *gv;
+	int err;
+
+	const size_t off_inbuf = 0;
+	const size_t off_comm = off_inbuf + sizeof(*inbuf);
+	const size_t off_pages = off_comm + TASK_COMM_LEN;
+	const size_t total_size = off_pages + sizeof(struct qda_phy_page);
+
+	if (init->file || init->filelen > QDA_FASTRPC_INIT_FILELEN_MAX)
+		return -EINVAL;
+
+	/* A signed process runs with the DSP's trust in the signature */
+	if (!(init->attrs & QDA_INIT_ATTR_UNSIGNED_MODULE) && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	/* The DSP loads the image into this memory and keeps using it while the process runs */
+	gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr,
+					QDA_FASTRPC_INIT_FILELEN_MAX, ctx->session);
+	if (IS_ERR(gem_obj))
+		return PTR_ERR(gem_obj);
+
+	ctx->init_mem_gem_obj = to_qda_gem_obj(gem_obj);
+
+	ctx->sc = init->attrs ? QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_CREATE_ATTR, 4, 0)
+			      : QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_CREATE, 4, 0);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 4);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, total_size);
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+	gv = kernel_gem->virt;
+
+	inbuf = (struct qda_fastrpc_create_process_inbuf *)(gv + off_inbuf);
+	inbuf->remote_session_id = ctx->session->remote_session_id;
+	inbuf->filelen = init->filelen;
+	inbuf->pageslen = 1;
+	inbuf->attrs = init->attrs;
+	inbuf->siglen = init->siglen;
+
+	strscpy(gv + off_comm, current->comm, TASK_COMM_LEN);
+	inbuf->namelen = strlen(gv + off_comm) + 1;
+
+	setup_pages_from_gem_obj(ctx->init_mem_gem_obj, (struct qda_phy_page *)(gv + off_pages));
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, inbuf, sizeof(*inbuf));
+	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;
+	assign_kernel_gem_arg(ctx, 3, kernel_gem, gv + off_pages, sizeof(struct qda_phy_page));
+
+	drm_gem_object_put(&kernel_gem->base);
+
+	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.
@@ -740,8 +913,23 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 	 */
 	qda_session_get(session);
 	ctx->session = session;
-
-	err = qda_fastrpc_prepare_args_invoke(ctx, argp);
+	ctx->pd = READ_ONCE(session->pd);
+
+	switch (ctx->type) {
+	case QDA_FASTRPC_RMID_INIT_RELEASE:
+		err = qda_fastrpc_prepare_args_release_process(ctx);
+		break;
+	case QDA_FASTRPC_RMID_INIT_CREATE:
+		/* The new process is in the user PD, and so is the request creating it */
+		ctx->pd = QDA_USER_PD;
+		err = qda_fastrpc_prepare_args_init_create(ctx, argp);
+		break;
+	case QDA_FASTRPC_RMID_INVOKE_DYNAMIC:
+		err = qda_fastrpc_prepare_args_invoke(ctx, argp);
+		break;
+	default:
+		return -EINVAL;
+	}
 	if (err)
 		return err;
 
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 3243bd1065c6..eaebc4e99676 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -33,14 +33,32 @@ struct qda_session;
 						 QDA_REMOTE_SCALARS_INHANDLES(sc) + \
 						 QDA_REMOTE_SCALARS_OUTHANDLES(sc))
 
+/* Encodes a method ID and the in/out buffer counts; handle counts are zero */
+#define QDA_FASTRPC_SCALARS(method, in, out)	((((method) & 0x1f) << 24) | \
+						 (((in) & 0xff) << 16) |     \
+						 (((out) & 0xff) << 8))
+
 /* FastRPC configuration constants */
 #define QDA_FASTRPC_ALIGN		128	/* Alignment the DSP expects of the payload */
 #define QDA_FASTRPC_MAX_HANDLELIST	16	/* Handles the DSP can ask to release */
 #define QDA_FASTRPC_MAX_CRCLIST		64	/* CRC list entries */
 
-/* Protection domain a session's invocations run in */
+/* Methods of the initialisation handle */
+#define QDA_FASTRPC_RMID_INIT_RELEASE		1
+#define QDA_FASTRPC_RMID_INIT_CREATE		6
+#define QDA_FASTRPC_RMID_INIT_CREATE_ATTR	7
+#define QDA_FASTRPC_RMID_INVOKE_DYNAMIC		0xFFFFFFFFU
+
+/* Handle the initialisation methods are addressed to */
+#define QDA_FASTRPC_INIT_HANDLE		0x1
+
+/* Protection domains a request can be directed at */
+#define QDA_ROOT_PD			0
 #define QDA_USER_PD			1
 
+/* Largest ELF image a process may be created from */
+#define QDA_FASTRPC_INIT_FILELEN_MAX	(4 * 1024 * 1024)
+
 /* Idle invoke header buffers a session keeps for reuse */
 #define QDA_MSG_BUF_POOL_MAX		8
 
@@ -111,6 +129,26 @@ struct qda_invoke_buf {
 	u32 pgidx;
 };
 
+/**
+ * struct qda_fastrpc_create_process_inbuf - Input buffer for process creation
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_create_process_inbuf {
+	/** @remote_session_id: Session the process belongs to */
+	int remote_session_id;
+	/** @namelen: Length of the process name including the NUL */
+	u32 namelen;
+	/** @filelen: Length of the ELF image in bytes */
+	u32 filelen;
+	/** @pageslen: Number of physical page descriptors */
+	u32 pageslen;
+	/** @attrs: Process attribute flags */
+	u32 attrs;
+	/** @siglen: Length of the signature data in bytes */
+	u32 siglen;
+};
+
 /**
  * struct qda_fastrpc_msg - FastRPC wire message for remote invocations
  *
@@ -164,6 +202,12 @@ struct qda_fastrpc_invoke_ctx {
 	u64 ctxid;
 	/** @retval: Status the DSP reported, or an error if it never answered */
 	int retval;
+	/** @pd: Protection domain, OR'ed into the low bits of @ctxid when packed */
+	int pd;
+	/** @type: Method being invoked (QDA_FASTRPC_RMID_*) */
+	u32 type;
+	/** @legacy: Arguments came from the legacy shim, where offsets are user VAs */
+	bool legacy;
 	/** @nscalars: Total number of scalar arguments */
 	int nscalars;
 	/** @nbufs: Number of buffer arguments; the rest are handle arguments */
@@ -180,6 +224,8 @@ struct qda_fastrpc_invoke_ctx {
 	struct drm_gem_object **gem_objs;
 	/** @msg_gem_obj: GEM object backing the message payload buffer */
 	struct qda_gem_obj *msg_gem_obj;
+	/** @init_mem_gem_obj: Memory the DSP loads a new process into */
+	struct qda_gem_obj *init_mem_gem_obj;
 	/** @pages: Array of physical page descriptors inside the payload */
 	struct qda_phy_page *pages;
 	/** @work: Completed once the DSP has answered */
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index e9f4151e344a..f51ad0bc9e9c 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -75,7 +75,22 @@ 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(void *data, struct qda_session *session)
+/*
+ * A process created again replaces the first, so the memory held for the
+ * previous one is dropped. Swapped atomically, as ioctls on one file can run
+ * concurrently.
+ */
+static void qda_session_set_init_mem(struct qda_session *session, struct qda_gem_obj *mem)
+{
+	struct qda_gem_obj *old;
+
+	drm_gem_object_get(&mem->base);
+	old = xchg(&session->init_mem_gem_obj, mem);
+	if (old)
+		drm_gem_object_put(&old->base);
+}
+
+static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
 {
 	struct qda_dev *qdev = session->qda_dev;
 	struct qda_fastrpc_invoke_ctx *ctx;
@@ -85,11 +100,26 @@ static int qda_fastrpc_invoke(void *data, struct qda_session *session)
 	if (IS_ERR(ctx))
 		return PTR_ERR(ctx);
 
+	ctx->type = type;
+
 	err = qda_fastrpc_prepare_args(ctx, session, data);
 	if (err)
 		goto out_put;
 
 	err = qda_fastrpc_invoke_common(qdev, ctx);
+	if (err)
+		goto out_put;
+
+	/*
+	 * The session only changes once the DSP has accepted the request, so a
+	 * failed create leaves the previous process, if any, as it was. The
+	 * memory is freed with the session, which the invocation keeps alive
+	 * until it is done, so a release needs no reference of its own.
+	 */
+	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
+		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
+		WRITE_ONCE(session->pd, QDA_USER_PD);
+	}
 
 out_put:
 	kref_put(&ctx->refcount, qda_fastrpc_context_free);
@@ -97,6 +127,31 @@ static int qda_fastrpc_invoke(void *data, struct qda_session *session)
 	return err;
 }
 
+/**
+ * qda_ioctl_init_create() - Create a DSP process
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_init_create)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+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);
+}
+
+/**
+ * qda_release_dsp_process() - Tear down a session's DSP process
+ * @qdev: QDA device structure
+ * @session: Session whose process is released
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
+{
+	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
+}
+
 /**
  * qda_ioctl_invoke() - Run a method on the DSP
  * @dev: DRM device structure
@@ -107,5 +162,5 @@ static int qda_fastrpc_invoke(void *data, struct qda_session *session)
  */
 int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_priv)
 {
-	return qda_fastrpc_invoke(data, file_priv->driver_priv);
+	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INVOKE_DYNAMIC, data, file_priv->driver_priv);
 }
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 3bb9cfd98370..da67f863c03c 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -12,5 +12,6 @@ int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_pr
 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);
 
 #endif /* __QDA_IOCTL_H__ */
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index c21bc5b2446c..75e67c5f5923 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -16,6 +16,7 @@ extern "C" {
 #define DRM_QDA_GEM_CREATE		0x01
 #define DRM_QDA_GEM_MMAP_OFFSET		0x02
 #define DRM_QDA_REMOTE_INVOKE		0x03
+#define DRM_QDA_REMOTE_SESSION_CREATE	0x04
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -25,6 +26,9 @@ extern "C" {
 					 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)
+#define DRM_IOCTL_QDA_REMOTE_SESSION_CREATE					\
+	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_SESSION_CREATE,		\
+		 struct drm_qda_init_create)
 
 /* Query type definitions for drm_qda_query */
 #define QDA_QUERY_DSP_NAME		1
@@ -109,6 +113,30 @@ struct drm_qda_invoke_args {
 	__u64 args;
 };
 
+/* Process attribute flags for drm_qda_init_create.attrs */
+#define QDA_INIT_ATTR_UNSIGNED_MODULE	(1U << 3)
+
+/**
+ * struct drm_qda_init_create - DSP process creation parameters
+ * @filelen: Length of the ELF image in bytes
+ * @filehandle: Handle of the buffer holding the ELF image
+ * @attrs: Process attribute flags
+ * @siglen: Length of the signature data in bytes
+ * @file: Reserved, must be zero
+ *
+ * Creates a process on the DSP for this file to run in. Without
+ * QDA_INIT_ATTR_UNSIGNED_MODULE in @attrs the process is created signed, which
+ * requires CAP_SYS_ADMIN. Creating a process again on the same file replaces
+ * the first.
+ */
+struct drm_qda_init_create {
+	__u32 filelen;
+	__u32 filehandle;
+	__u32 attrs;
+	__u32 siglen;
+	__u64 file;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (14 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 15/25] accel/qda: Add DSP process creation and release Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:48   ` sashiko-bot
  2026-11-02  8:47   ` Krzysztof Kozlowski
  2026-11-01 22:21 ` [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space Ekansh Gupta
                   ` (9 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

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@qti.qualcomm.com>
---
 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


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

* [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (15 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:54   ` sashiko-bot
  2026-11-02  9:24   ` Konrad Dybcio
  2026-11-01 22:21 ` [PATCH v4 18/25] accel/qda: Add remote memory unmapping from " Ekansh Gupta
                   ` (8 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

The DSP can only touch memory it has been told about, so user space
needs a way to make a buffer visible to it ahead of time rather than
only per call.

Add an ioctl that maps a buffer, named by handle, into the DSP's address
space of the calling session and returns the address the DSP will use.
There are two flavours, selected by the request: a plain mapping of the
whole buffer, optionally at an address the caller suggests, and a
mapping of a region of it with caller-chosen attributes and a buffer
identifier the DSP tracks.

The length of the mapping is checked against the size of the buffer, so
a mapping cannot be described as larger than what backs it. Buffers are
looked up in the calling session's own handle table.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |   1 +
 drivers/accel/qda/qda_fastrpc.c | 190 +++++++++++++++++++++++++++++++++++++++-
 drivers/accel/qda/qda_fastrpc.h |  51 +++++++++++
 drivers/accel/qda/qda_ioctl.c   |  25 ++++++
 drivers/accel/qda/qda_ioctl.h   |   1 +
 include/uapi/drm/qda_accel.h    |  36 ++++++++
 6 files changed, 303 insertions(+), 1 deletion(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 983c6a3dcfb4..7f19380701d9 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -125,6 +125,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_GEM_MMAP_OFFSET, qda_ioctl_gem_mmap_offset, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_CREATE, qda_ioctl_init_create, 0),
+	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MMAP, qda_ioctl_mmap, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 35ce60f3de2e..744d7317cbe1 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -3,6 +3,7 @@
 #include <linux/bitfield.h>
 #include <linux/capability.h>
 #include <linux/dma-mapping.h>
+#include <linux/limits.h>
 #include <linux/mm.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
@@ -251,6 +252,9 @@ void qda_fastrpc_context_free(struct kref *ref)
 	if (ctx->init_mem_gem_obj)
 		drm_gem_object_put(&ctx->init_mem_gem_obj->base);
 
+	if (ctx->map_obj)
+		drm_gem_object_put(ctx->map_obj);
+
 	if (ctx->session)
 		qda_session_put(ctx->session);
 
@@ -326,7 +330,10 @@ static struct qda_gem_obj *alloc_kernel_gem(struct qda_fastrpc_invoke_ctx *ctx,
 	return to_qda_gem_obj(gem_obj);
 }
 
-/* The context releases whatever has been allocated here if a later step fails */
+/*
+ * The context releases whatever has been allocated here if a later step
+ * fails, which needs @nscalars to cover every slot that may hold a reference.
+ */
 static int alloc_kernel_args(struct qda_fastrpc_invoke_ctx *ctx, u32 nargs)
 {
 	ctx->args = kzalloc_objs(*ctx->args, nargs);
@@ -337,6 +344,8 @@ static int alloc_kernel_args(struct qda_fastrpc_invoke_ctx *ctx, u32 nargs)
 	if (!ctx->gem_objs)
 		return -ENOMEM;
 
+	ctx->nscalars = nargs;
+
 	return 0;
 }
 
@@ -613,6 +622,37 @@ void qda_fastrpc_flush_pending(struct qda_dev *qdev)
 	}
 }
 
+/**
+ * qda_fastrpc_return_result() - Copy what a request returns into the ioctl data
+ * @ctx: Invocation that completed successfully
+ * @argp: Kernel-side ioctl argument buffer owned by the DRM core
+ *
+ * Buffer arguments are written by the DSP straight into memory the caller can
+ * see, so only the mapping requests have a value to surface, which the DSP
+ * wrote into the response slot the driver built for it.
+ */
+void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct drm_qda_mem_map *margs = argp;
+	struct qda_fastrpc_map_rsp_msg *rsp;
+	int idx;
+
+	switch (ctx->type) {
+	case QDA_FASTRPC_RMID_INIT_MMAP:
+		idx = 2;
+		break;
+	case QDA_FASTRPC_RMID_INIT_MEM_MAP:
+		idx = 3;
+		break;
+	default:
+		return;
+	}
+
+	rsp = (struct qda_fastrpc_map_rsp_msg *)
+	      ((u8 *)to_qda_gem_obj(ctx->gem_objs[idx])->virt + ctx->args[idx].offset);
+	margs->vaddrout = rsp->vaddrout;
+}
+
 /**
  * qda_fastrpc_invoke_common() - Shared tail of a FastRPC invocation
  * @qdev: Device to invoke on
@@ -890,6 +930,148 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
 	return err;
 }
 
+/*
+ * Find the buffer a mapping request names and describe it to the DSP. The
+ * buffer is held by the context until the request is done, so that closing its
+ * handle meanwhile cannot free memory the DSP is being told to map. The
+ * region must fit in the buffer, as the DSP would otherwise map past its end.
+ */
+static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct drm_qda_mem_map *margs,
+			    struct qda_phy_page *pages)
+{
+	struct qda_gem_obj *qda_gem_obj;
+	int err;
+
+	if (!margs->handle)
+		return -EINVAL;
+
+	err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
+	if (err)
+		return err;
+
+	qda_gem_obj = to_qda_gem_obj(ctx->map_obj);
+	if (margs->size > qda_gem_obj->size)
+		return -EINVAL;
+
+	pages->addr = qda_gem_obj->dma_addr;
+	pages->size = margs->size ? margs->size : qda_gem_obj->size;
+
+	return 0;
+}
+
+/*
+ * INIT_MMAP packed layout:
+ *   [0] req	sizeof(qda_fastrpc_map_req_msg)		IN
+ *   [1] pages	sizeof(qda_phy_page)			IN
+ *   [2] rsp	sizeof(qda_fastrpc_map_rsp_msg)		OUT
+ */
+static int qda_fastrpc_prepare_args_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct drm_qda_mem_map *margs = argp;
+	struct qda_fastrpc_map_req_msg *req;
+	struct qda_gem_obj *kernel_gem;
+	u8 *gv;
+	int err;
+
+	const size_t off_req = 0;
+	const size_t off_pages = off_req + sizeof(*req);
+	const size_t off_rsp = off_pages + sizeof(struct qda_phy_page);
+	const size_t total_size = off_rsp + sizeof(struct qda_fastrpc_map_rsp_msg);
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MMAP, 2, 1);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 3);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, total_size);
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+	gv = kernel_gem->virt;
+
+	req = (struct qda_fastrpc_map_req_msg *)(gv + off_req);
+	req->remote_session_id = ctx->session->remote_session_id;
+	req->flags = margs->flags;
+	req->vaddr = margs->vaddrin;
+	req->num = sizeof(struct qda_phy_page);
+
+	err = setup_mmap_pages(ctx, margs, (struct qda_phy_page *)(gv + off_pages));
+	if (err)
+		goto out_put;
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, gv + off_req, sizeof(*req));
+	assign_kernel_gem_arg(ctx, 1, kernel_gem, gv + off_pages, sizeof(struct qda_phy_page));
+	assign_kernel_gem_arg(ctx, 2, kernel_gem, gv + off_rsp,
+			      sizeof(struct qda_fastrpc_map_rsp_msg));
+
+out_put:
+	drm_gem_object_put(&kernel_gem->base);
+
+	return err;
+}
+
+/*
+ * INIT_MEM_MAP packed layout:
+ *   [0] req	sizeof(qda_fastrpc_mem_map_req_msg)	IN
+ *   [1] pages	sizeof(qda_phy_page)			IN
+ *   [2] empty	0 bytes, which the DSP requires		IN
+ *   [3] rsp	sizeof(qda_fastrpc_map_rsp_msg)		OUT
+ */
+static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct drm_qda_mem_map *margs = argp;
+	struct qda_fastrpc_mem_map_req_msg *req;
+	struct qda_gem_obj *kernel_gem;
+	u8 *gv;
+	int err;
+
+	const size_t off_req = 0;
+	const size_t off_pages = off_req + sizeof(*req);
+	const size_t off_rsp = off_pages + sizeof(struct qda_phy_page);
+	const size_t total_size = off_rsp + sizeof(struct qda_fastrpc_map_rsp_msg);
+
+	/* The wire field is signed */
+	if (margs->offset > INT_MAX)
+		return -EINVAL;
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MEM_MAP, 3, 1);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 4);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, total_size);
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+	gv = kernel_gem->virt;
+
+	req = (struct qda_fastrpc_mem_map_req_msg *)(gv + off_req);
+	req->remote_session_id = ctx->session->remote_session_id;
+	req->handle = margs->dsp_handle;
+	req->offset = margs->offset;
+	req->flags = margs->flags;
+	req->vaddrin = margs->vaddrin;
+	req->num = sizeof(struct qda_phy_page);
+	req->data_len = 0;
+
+	err = setup_mmap_pages(ctx, margs, (struct qda_phy_page *)(gv + off_pages));
+	if (err)
+		goto out_put;
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, gv + off_req, sizeof(*req));
+	assign_kernel_gem_arg(ctx, 1, kernel_gem, gv + off_pages, sizeof(struct qda_phy_page));
+	assign_kernel_gem_arg(ctx, 2, kernel_gem, gv + off_pages, 0);
+	assign_kernel_gem_arg(ctx, 3, kernel_gem, gv + off_rsp,
+			      sizeof(struct qda_fastrpc_map_rsp_msg));
+
+out_put:
+	drm_gem_object_put(&kernel_gem->base);
+
+	return err;
+}
+
 /*
  * INVOKE_DYNAMIC: the argument descriptors are supplied by user space, which
  * is also responsible for having imported every buffer to a GEM handle.
@@ -953,6 +1135,12 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 		ctx->pd = QDA_USER_PD;
 		err = qda_fastrpc_prepare_args_init_create(ctx, argp);
 		break;
+	case QDA_FASTRPC_RMID_INIT_MMAP:
+		err = qda_fastrpc_prepare_args_map(ctx, argp);
+		break;
+	case QDA_FASTRPC_RMID_INIT_MEM_MAP:
+		err = qda_fastrpc_prepare_args_mem_map(ctx, argp);
+		break;
 	case QDA_FASTRPC_RMID_INVOKE_DYNAMIC:
 		err = qda_fastrpc_prepare_args_invoke(ctx, argp);
 		break;
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index a5bd7661c26c..1bd696baa95c 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -45,8 +45,10 @@ struct qda_session;
 
 /* Methods of the initialisation handle */
 #define QDA_FASTRPC_RMID_INIT_RELEASE		1
+#define QDA_FASTRPC_RMID_INIT_MMAP		4
 #define QDA_FASTRPC_RMID_INIT_CREATE		6
 #define QDA_FASTRPC_RMID_INIT_CREATE_ATTR	7
+#define QDA_FASTRPC_RMID_INIT_MEM_MAP		10
 #define QDA_FASTRPC_RMID_INVOKE_DYNAMIC		0xFFFFFFFFU
 
 /* Handle the initialisation methods are addressed to */
@@ -149,6 +151,52 @@ struct qda_fastrpc_create_process_inbuf {
 	u32 siglen;
 };
 
+/**
+ * struct qda_fastrpc_map_req_msg - Request to map a whole buffer
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_map_req_msg {
+	/** @remote_session_id: Session the mapping belongs to */
+	s32 remote_session_id;
+	/** @flags: Mapping flags (cache attributes, permissions) */
+	u32 flags;
+	/** @vaddr: Address the DSP should map the buffer at, if it can */
+	u64 vaddr;
+	/** @num: Size of the physical page descriptor in bytes */
+	s32 num;
+};
+
+/**
+ * struct qda_fastrpc_mem_map_req_msg - Request to map a region with attributes
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_mem_map_req_msg {
+	/** @remote_session_id: Session the mapping belongs to */
+	s32 remote_session_id;
+	/** @handle: Identifier the DSP tracks the buffer by */
+	s32 handle;
+	/** @offset: Offset within the buffer */
+	s32 offset;
+	/** @flags: Mapping flags (cache attributes, permissions) */
+	u32 flags;
+	/** @vaddrin: Address the DSP should map the buffer at, if it can */
+	u64 vaddrin;
+	/** @num: Size of the physical page descriptor in bytes */
+	s32 num;
+	/** @data_len: Length of additional inline data */
+	s32 data_len;
+};
+
+/**
+ * struct qda_fastrpc_map_rsp_msg - The DSP's answer to a map request
+ */
+struct qda_fastrpc_map_rsp_msg {
+	/** @vaddrout: Address the DSP mapped the buffer at */
+	u64 vaddrout;
+};
+
 /**
  * struct qda_fastrpc_msg - FastRPC wire message for remote invocations
  *
@@ -226,6 +274,8 @@ struct qda_fastrpc_invoke_ctx {
 	struct qda_gem_obj *msg_gem_obj;
 	/** @init_mem_gem_obj: Memory the DSP loads a new process into */
 	struct qda_gem_obj *init_mem_gem_obj;
+	/** @map_obj: Buffer being mapped into the DSP, held until the request is done */
+	struct drm_gem_object *map_obj;
 	/** @pages: Array of physical page descriptors inside the payload */
 	struct qda_phy_page *pages;
 	/** @work: Completed once the DSP has answered */
@@ -285,6 +335,7 @@ void qda_session_msg_buf_pool_destroy(struct qda_session *session);
 int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 			     struct qda_session *session, void *argp);
 int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg);
+void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp);
 int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ctx *ctx);
 void qda_fastrpc_flush_pending(struct qda_dev *qdev);
 int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 99b8a2517726..cd3cea72f50e 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -110,6 +110,8 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
 	if (err)
 		goto out_put;
 
+	qda_fastrpc_return_result(ctx, data);
+
 	/*
 	 * The session only changes once the DSP has accepted the request, so a
 	 * failed create leaves the previous process, if any, as it was. The
@@ -172,6 +174,29 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
 	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
 }
 
+/**
+ * qda_ioctl_mmap() - Map a buffer into the DSP's address space
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_mem_map)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_mem_map *map_req = data;
+
+	switch (map_req->request) {
+	case QDA_REQUEST_MMAP:
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MMAP, data, file_priv->driver_priv);
+	case QDA_REQUEST_MEM_MAP:
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_MAP, data,
+					  file_priv->driver_priv);
+	default:
+		return -EINVAL;
+	}
+}
+
 /**
  * qda_ioctl_invoke() - Run a method on the DSP
  * @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index b2c7bd25b138..afb3ed0e132e 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -15,6 +15,7 @@ int qda_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *fi
 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_ioctl_mmap(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/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 75e67c5f5923..bd2d0f4308b1 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -17,6 +17,7 @@ extern "C" {
 #define DRM_QDA_GEM_MMAP_OFFSET		0x02
 #define DRM_QDA_REMOTE_INVOKE		0x03
 #define DRM_QDA_REMOTE_SESSION_CREATE	0x04
+#define DRM_QDA_REMOTE_MMAP		0x05
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -29,6 +30,12 @@ extern "C" {
 #define DRM_IOCTL_QDA_REMOTE_SESSION_CREATE					\
 	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_SESSION_CREATE,		\
 		 struct drm_qda_init_create)
+#define DRM_IOCTL_QDA_REMOTE_MMAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_MMAP, \
+					 struct drm_qda_mem_map)
+
+/* Request types for drm_qda_mem_map.request */
+#define QDA_REQUEST_MMAP	1	/* Map a whole buffer */
+#define QDA_REQUEST_MEM_MAP	2	/* Map a region of a buffer, with attributes */
 
 /* Query type definitions for drm_qda_query */
 #define QDA_QUERY_DSP_NAME		1
@@ -137,6 +144,35 @@ struct drm_qda_init_create {
 	__u64 file;
 };
 
+/**
+ * struct drm_qda_mem_map - Parameters for mapping a buffer into the DSP
+ * @request: QDA_REQUEST_MMAP or QDA_REQUEST_MEM_MAP
+ * @flags: Mapping flags for the DSP (cache attributes, permissions)
+ * @handle: Handle of the buffer to map
+ * @attrs: Mapping attributes (QDA_REQUEST_MEM_MAP)
+ * @offset: Offset within the buffer (QDA_REQUEST_MEM_MAP), at most INT_MAX
+ * @dsp_handle: Identifier the DSP tracks the buffer by (QDA_REQUEST_MEM_MAP)
+ * @vaddrin: Address the DSP should map the buffer at, if it can (input)
+ * @size: Length of the region to map in bytes, or 0 for the whole buffer
+ * @vaddrout: Address the DSP mapped the buffer at (output)
+ *
+ * Makes a buffer visible to the DSP of this file ahead of the calls that use
+ * it. QDA_REQUEST_MMAP maps a whole buffer and uses @handle, @flags,
+ * @vaddrin, @size and @vaddrout. QDA_REQUEST_MEM_MAP maps a region of it with
+ * caller-chosen attributes and uses every field.
+ */
+struct drm_qda_mem_map {
+	__u32 request;
+	__u32 flags;
+	__u32 handle;
+	__u32 attrs;
+	__u32 offset;
+	__s32 dsp_handle;
+	__u64 vaddrin;
+	__u64 size;
+	__u64 vaddrout;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 18/25] accel/qda: Add remote memory unmapping from the DSP address space
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (16 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:49   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim Ekansh Gupta
                   ` (7 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

A buffer mapped for the DSP stays mapped until it is explicitly
unmapped, so user space needs a way to take a mapping down once it is
no longer needed.

Add an ioctl that removes a mapping, naming it by the address that map
returned. As with map there are two flavours, selected by the request:
one matching the plain mapping and one matching the attribute-based
mapping, which also carries the DSP-side buffer identifier.

The buffer itself is kept alive by the handle it was created or
imported under, so unmapping does not touch it; the mapping and the
buffer have independent lifetimes and user space is expected to keep
the handle open for as long as the DSP uses the mapping.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |  1 +
 drivers/accel/qda/qda_fastrpc.c | 73 +++++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h | 32 ++++++++++++++++++
 drivers/accel/qda/qda_ioctl.c   | 24 ++++++++++++++
 drivers/accel/qda/qda_ioctl.h   |  1 +
 include/uapi/drm/qda_accel.h    | 30 +++++++++++++++++
 6 files changed, 161 insertions(+)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 7f19380701d9..c5b41b869692 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -126,6 +126,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_CREATE, qda_ioctl_init_create, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MMAP, qda_ioctl_mmap, 0),
+	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MUNMAP, qda_ioctl_munmap, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 744d7317cbe1..dbd14cd07cb2 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -1072,6 +1072,73 @@ static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx,
 	return err;
 }
 
+/*
+ * INIT_MUNMAP packed layout:
+ *   [0] req	sizeof(qda_fastrpc_munmap_req_msg)	IN
+ */
+static int qda_fastrpc_prepare_args_munmap(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct drm_qda_mem_unmap *uargs = argp;
+	struct qda_fastrpc_munmap_req_msg *req;
+	struct qda_gem_obj *kernel_gem;
+	int err;
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MUNMAP, 1, 0);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 1);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, sizeof(*req));
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+
+	req = kernel_gem->virt;
+	req->remote_session_id = ctx->session->remote_session_id;
+	req->vaddr = uargs->vaddrout;
+	req->size = uargs->size;
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, req, sizeof(*req));
+	drm_gem_object_put(&kernel_gem->base);
+
+	return 0;
+}
+
+/*
+ * INIT_MEM_UNMAP packed layout:
+ *   [0] req	sizeof(qda_fastrpc_mem_unmap_req_msg)	IN
+ */
+static int qda_fastrpc_prepare_args_mem_unmap(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct drm_qda_mem_unmap *uargs = argp;
+	struct qda_fastrpc_mem_unmap_req_msg *req;
+	struct qda_gem_obj *kernel_gem;
+	int err;
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, 1, 0);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 1);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, sizeof(*req));
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+
+	req = kernel_gem->virt;
+	req->remote_session_id = ctx->session->remote_session_id;
+	req->handle = uargs->dsp_handle;
+	req->vaddrin = uargs->vaddr;
+	req->len = uargs->size;
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, req, sizeof(*req));
+	drm_gem_object_put(&kernel_gem->base);
+
+	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.
@@ -1141,6 +1208,12 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 	case QDA_FASTRPC_RMID_INIT_MEM_MAP:
 		err = qda_fastrpc_prepare_args_mem_map(ctx, argp);
 		break;
+	case QDA_FASTRPC_RMID_INIT_MUNMAP:
+		err = qda_fastrpc_prepare_args_munmap(ctx, argp);
+		break;
+	case QDA_FASTRPC_RMID_INIT_MEM_UNMAP:
+		err = qda_fastrpc_prepare_args_mem_unmap(ctx, argp);
+		break;
 	case QDA_FASTRPC_RMID_INVOKE_DYNAMIC:
 		err = qda_fastrpc_prepare_args_invoke(ctx, argp);
 		break;
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 1bd696baa95c..889b41dc20b0 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -46,9 +46,11 @@ struct qda_session;
 /* Methods of the initialisation handle */
 #define QDA_FASTRPC_RMID_INIT_RELEASE		1
 #define QDA_FASTRPC_RMID_INIT_MMAP		4
+#define QDA_FASTRPC_RMID_INIT_MUNMAP		5
 #define QDA_FASTRPC_RMID_INIT_CREATE		6
 #define QDA_FASTRPC_RMID_INIT_CREATE_ATTR	7
 #define QDA_FASTRPC_RMID_INIT_MEM_MAP		10
+#define QDA_FASTRPC_RMID_INIT_MEM_UNMAP		11
 #define QDA_FASTRPC_RMID_INVOKE_DYNAMIC		0xFFFFFFFFU
 
 /* Handle the initialisation methods are addressed to */
@@ -197,6 +199,36 @@ struct qda_fastrpc_map_rsp_msg {
 	u64 vaddrout;
 };
 
+/**
+ * struct qda_fastrpc_munmap_req_msg - Request to remove a plain mapping
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_munmap_req_msg {
+	/** @remote_session_id: Session the mapping belongs to */
+	s32 remote_session_id;
+	/** @vaddr: Address the mapping was made at */
+	u64 vaddr;
+	/** @size: Length of the mapping in bytes */
+	u64 size;
+};
+
+/**
+ * struct qda_fastrpc_mem_unmap_req_msg - Request to remove an attribute-based mapping
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_mem_unmap_req_msg {
+	/** @remote_session_id: Session the mapping belongs to */
+	s32 remote_session_id;
+	/** @handle: Identifier the DSP tracks the buffer by */
+	s32 handle;
+	/** @vaddrin: Address the mapping was made at */
+	u64 vaddrin;
+	/** @len: Length of the mapping in bytes */
+	u64 len;
+};
+
 /**
  * struct qda_fastrpc_msg - FastRPC wire message for remote invocations
  *
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index cd3cea72f50e..17d1b488b4a9 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -197,6 +197,30 @@ int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_pri
 	}
 }
 
+/**
+ * qda_ioctl_munmap() - Remove a buffer's mapping from the DSP's address space
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_mem_unmap)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_mem_unmap *unmap_req = data;
+
+	switch (unmap_req->request) {
+	case QDA_REQUEST_MUNMAP:
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MUNMAP, data,
+					  file_priv->driver_priv);
+	case QDA_REQUEST_MEM_UNMAP:
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, data,
+					  file_priv->driver_priv);
+	default:
+		return -EINVAL;
+	}
+}
+
 /**
  * qda_ioctl_invoke() - Run a method on the DSP
  * @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index afb3ed0e132e..9c053349c3fa 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -16,6 +16,7 @@ int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void *data, struct drm_fil
 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_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_ioctl_munmap(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/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index bd2d0f4308b1..43598ddef461 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -18,6 +18,7 @@ extern "C" {
 #define DRM_QDA_REMOTE_INVOKE		0x03
 #define DRM_QDA_REMOTE_SESSION_CREATE	0x04
 #define DRM_QDA_REMOTE_MMAP		0x05
+#define DRM_QDA_REMOTE_MUNMAP		0x06
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -32,11 +33,17 @@ extern "C" {
 		 struct drm_qda_init_create)
 #define DRM_IOCTL_QDA_REMOTE_MMAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_MMAP, \
 					 struct drm_qda_mem_map)
+#define DRM_IOCTL_QDA_REMOTE_MUNMAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_MUNMAP, \
+					 struct drm_qda_mem_unmap)
 
 /* Request types for drm_qda_mem_map.request */
 #define QDA_REQUEST_MMAP	1	/* Map a whole buffer */
 #define QDA_REQUEST_MEM_MAP	2	/* Map a region of a buffer, with attributes */
 
+/* Request types for drm_qda_mem_unmap.request */
+#define QDA_REQUEST_MUNMAP	1
+#define QDA_REQUEST_MEM_UNMAP	2
+
 /* Query type definitions for drm_qda_query */
 #define QDA_QUERY_DSP_NAME		1
 
@@ -173,6 +180,29 @@ struct drm_qda_mem_map {
 	__u64 vaddrout;
 };
 
+/**
+ * struct drm_qda_mem_unmap - Parameters for removing a DSP mapping
+ * @request: QDA_REQUEST_MUNMAP or QDA_REQUEST_MEM_UNMAP
+ * @dsp_handle: Identifier the DSP tracks the buffer by (QDA_REQUEST_MEM_UNMAP)
+ * @vaddr: Address the mapping was made at (QDA_REQUEST_MEM_UNMAP)
+ * @vaddrout: Address the mapping was made at (QDA_REQUEST_MUNMAP)
+ * @size: Length of the mapping in bytes
+ *
+ * Removes a mapping made with DRM_IOCTL_QDA_REMOTE_MMAP, named by the address
+ * that call returned in drm_qda_mem_map.vaddrout. The request must match the
+ * one the mapping was made with: QDA_REQUEST_MUNMAP uses @vaddrout and @size,
+ * QDA_REQUEST_MEM_UNMAP uses @dsp_handle, @vaddr and @size. The buffer is not
+ * affected, so its handle must stay open for as long as the DSP uses the
+ * mapping.
+ */
+struct drm_qda_mem_unmap {
+	__u32 request;
+	__s32 dsp_handle;
+	__u64 vaddr;
+	__u64 vaddrout;
+	__u64 size;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (17 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 18/25] accel/qda: Add remote memory unmapping from " Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:57   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains Ekansh Gupta
                   ` (6 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Applications written for the /dev/fastrpc-* interface name buffers by
file descriptor rather than GEM handle, and have no handle table to
keep a mapped buffer alive between the map and unmap calls. Serve the
four legacy map and unmap ioctls by importing the descriptor as usual,
and track the live mapping on the open file so the buffer stays
referenced for as long as the DSP has it mapped, exactly the job a GEM
handle does for the native UAPI.

The buffer reaches the shared mapping code already imported, in place
of a handle. As for a buffer named by handle, it must be mapped in the
calling session's context bank, since a descriptor can name a buffer
another session exported, and the requested length must fit in it.

Two lists are kept per open file, since the two legacy unmap paths name
a mapping differently: MUNMAP only ever gets back the address map
returned, while MEM_UNMAP uses the file descriptor as the identifier
the DSP tracks the buffer by. A mapping is recorded with the address
and length it was made with, and an unmap must match them: the length
the DSP is asked to unmap is the recorded one, and MUNMAP with a
different length is refused. The same descriptor cannot be mapped
twice with MEM_MAP, as the DSP could not tell the two apart on unmap,
and a negative buffer offset is refused.

If the DSP cannot unmap a mapping, it stays recorded so the unmap can
be retried. If the mapping cannot be reported back to the caller, it is
taken down again rather than left unreachable. Mappings still
outstanding when the file closes are unmapped on the DSP before the
buffers they hold are released, so the DSP is never left using memory
that has been freed.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_fastrpc.c      |  40 ++++--
 drivers/accel/qda/qda_fastrpc.h      |  17 +++
 drivers/accel/qda/qda_fastrpc_shim.c | 262 +++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.c        |  64 ++++++---
 drivers/accel/qda/qda_ioctl.h        |   4 +
 drivers/accel/qda/qda_legacy_ops.c   |  77 ++++++++++
 drivers/accel/qda/qda_legacy_ops.h   |  11 ++
 7 files changed, 447 insertions(+), 28 deletions(-)

diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index dbd14cd07cb2..b35604fce630 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -633,7 +633,7 @@ void qda_fastrpc_flush_pending(struct qda_dev *qdev)
  */
 void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
 {
-	struct drm_qda_mem_map *margs = argp;
+	struct qda_mem_map_args *map_args = argp;
 	struct qda_fastrpc_map_rsp_msg *rsp;
 	int idx;
 
@@ -650,7 +650,7 @@ void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
 
 	rsp = (struct qda_fastrpc_map_rsp_msg *)
 	      ((u8 *)to_qda_gem_obj(ctx->gem_objs[idx])->virt + ctx->args[idx].offset);
-	margs->vaddrout = rsp->vaddrout;
+	map_args->map.vaddrout = rsp->vaddrout;
 }
 
 /**
@@ -935,19 +935,33 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
  * buffer is held by the context until the request is done, so that closing its
  * handle meanwhile cannot free memory the DSP is being told to map. The
  * region must fit in the buffer, as the DSP would otherwise map past its end.
+ *
+ * A caller with no handle table supplies the buffer already imported. It is
+ * checked as a buffer named by handle would be: an fd can name a buffer that
+ * another session exported, which resolves to the original object, still
+ * mapped in that session's context bank.
  */
-static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct drm_qda_mem_map *margs,
+static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct qda_mem_map_args *args,
 			    struct qda_phy_page *pages)
 {
+	struct drm_qda_mem_map *margs = &args->map;
 	struct qda_gem_obj *qda_gem_obj;
 	int err;
 
-	if (!margs->handle)
-		return -EINVAL;
+	if (args->obj) {
+		if (to_qda_gem_obj(args->obj)->iommu_dev != ctx->session->assigned_iommu_dev)
+			return -EACCES;
 
-	err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
-	if (err)
-		return err;
+		drm_gem_object_get(args->obj);
+		ctx->map_obj = args->obj;
+	} else {
+		if (!margs->handle)
+			return -EINVAL;
+
+		err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
+		if (err)
+			return err;
+	}
 
 	qda_gem_obj = to_qda_gem_obj(ctx->map_obj);
 	if (margs->size > qda_gem_obj->size)
@@ -967,7 +981,8 @@ static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct drm_qda_m
  */
 static int qda_fastrpc_prepare_args_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
 {
-	struct drm_qda_mem_map *margs = argp;
+	struct qda_mem_map_args *map_args = argp;
+	struct drm_qda_mem_map *margs = &map_args->map;
 	struct qda_fastrpc_map_req_msg *req;
 	struct qda_gem_obj *kernel_gem;
 	u8 *gv;
@@ -996,7 +1011,7 @@ static int qda_fastrpc_prepare_args_map(struct qda_fastrpc_invoke_ctx *ctx, void
 	req->vaddr = margs->vaddrin;
 	req->num = sizeof(struct qda_phy_page);
 
-	err = setup_mmap_pages(ctx, margs, (struct qda_phy_page *)(gv + off_pages));
+	err = setup_mmap_pages(ctx, map_args, (struct qda_phy_page *)(gv + off_pages));
 	if (err)
 		goto out_put;
 
@@ -1020,7 +1035,8 @@ static int qda_fastrpc_prepare_args_map(struct qda_fastrpc_invoke_ctx *ctx, void
  */
 static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
 {
-	struct drm_qda_mem_map *margs = argp;
+	struct qda_mem_map_args *map_args = argp;
+	struct drm_qda_mem_map *margs = &map_args->map;
 	struct qda_fastrpc_mem_map_req_msg *req;
 	struct qda_gem_obj *kernel_gem;
 	u8 *gv;
@@ -1056,7 +1072,7 @@ static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx,
 	req->num = sizeof(struct qda_phy_page);
 	req->data_len = 0;
 
-	err = setup_mmap_pages(ctx, margs, (struct qda_phy_page *)(gv + off_pages));
+	err = setup_mmap_pages(ctx, map_args, (struct qda_phy_page *)(gv + off_pages));
 	if (err)
 		goto out_put;
 
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 889b41dc20b0..10a99bbfa383 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -360,6 +360,23 @@ struct qda_init_create_args {
 	struct drm_gem_object *elf_obj;
 };
 
+/**
+ * struct qda_mem_map_args - Kernel-side arguments for mapping a buffer into the DSP
+ *
+ * Wraps the UAPI arguments so that a caller with no handle table, such as the
+ * legacy shim, can pass an already imported buffer.
+ */
+struct qda_mem_map_args {
+	/** @map: Mapping parameters; @map.vaddrout is filled in on success */
+	struct drm_qda_mem_map map;
+	/**
+	 * @obj: Buffer to map, in the session's context bank, or NULL if
+	 * @map.handle names it. Borrowed: the caller keeps its reference for
+	 * the length of the call.
+	 */
+	struct drm_gem_object *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 4a2ba20ee01a..c46d2de4f581 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -8,7 +8,9 @@
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/limits.h>
+#include <linux/list.h>
 #include <linux/miscdevice.h>
+#include <linux/mutex.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/slab.h>
@@ -21,9 +23,29 @@
 #include "qda_fastrpc.h"
 #include "qda_legacy_ops.h"
 
+/*
+ * A buffer the DSP has mapped for an open file. A legacy session has no
+ * handle table, so this is what keeps the buffer alive for as long as the
+ * mapping lasts, the job a GEM handle does for the accel UAPI.
+ *
+ * Plain mappings are found by the address map returned, which is all
+ * MUNMAP carries. Attribute-based ones are found by the fd, which doubles
+ * as the identifier the DSP tracks the buffer by.
+ */
+struct qda_shim_mapping {
+	struct list_head node;
+	u64 key;
+	u64 vaddr;
+	u64 size;
+	struct drm_gem_object *obj;
+};
+
 /* State of one open /dev/fastrpc-* file */
 struct qda_shim_file {
 	struct qda_session *session;
+	struct mutex maps_lock;
+	struct list_head mmap_list;
+	struct list_head mem_map_list;
 	/* Opened through the /dev/fastrpc-<dsp>-secure node */
 	bool secure_node;
 	/* Properties of the qda_shim_dev this file was opened on */
@@ -79,6 +101,83 @@ static struct drm_gem_object *shim_import_fd(struct qda_session *session, s32 fd
 	return obj;
 }
 
+/* Takes ownership of @obj on success; on failure the caller still owns it */
+static struct qda_shim_mapping *shim_mapping_add(struct qda_shim_file *sf,
+						 struct list_head *list, u64 key, u64 vaddr,
+						 u64 size, struct drm_gem_object *obj)
+{
+	struct qda_shim_mapping *m, *new;
+
+	new = kzalloc_obj(*new);
+	if (!new)
+		return ERR_PTR(-ENOMEM);
+
+	new->key = key;
+	new->vaddr = vaddr;
+	new->size = size;
+	new->obj = obj;
+
+	guard(mutex)(&sf->maps_lock);
+
+	list_for_each_entry(m, list, node) {
+		if (m->key == key) {
+			kfree(new);
+			return ERR_PTR(-EEXIST);
+		}
+	}
+	list_add_tail(&new->node, list);
+
+	return new;
+}
+
+static struct qda_shim_mapping *shim_mapping_detach(struct qda_shim_file *sf,
+						    struct list_head *list, u64 key, u64 vaddr)
+{
+	struct qda_shim_mapping *m;
+
+	guard(mutex)(&sf->maps_lock);
+
+	list_for_each_entry(m, list, node) {
+		if (m->key == key && m->vaddr == vaddr) {
+			list_del(&m->node);
+			return m;
+		}
+	}
+
+	return NULL;
+}
+
+static void shim_mapping_attach(struct qda_shim_file *sf, struct list_head *list,
+				struct qda_shim_mapping *m)
+{
+	guard(mutex)(&sf->maps_lock);
+	list_add_tail(&m->node, list);
+}
+
+static void shim_mapping_free(struct qda_shim_mapping *m)
+{
+	drm_gem_object_put(m->obj);
+	kfree(m);
+}
+
+/* The DSP unmap comes first: the buffer must outlive the DSP's use of it */
+static void shim_mappings_release(struct qda_shim_file *sf)
+{
+	struct qda_shim_mapping *m, *tmp;
+
+	list_for_each_entry_safe(m, tmp, &sf->mmap_list, node) {
+		list_del(&m->node);
+		qda_legacy_ops_table.munmap(sf->session, m->vaddr, m->size);
+		shim_mapping_free(m);
+	}
+
+	list_for_each_entry_safe(m, tmp, &sf->mem_map_list, node) {
+		list_del(&m->node);
+		qda_legacy_ops_table.mem_unmap(sf->session, m->key, m->vaddr, m->size);
+		shim_mapping_free(m);
+	}
+}
+
 static int qda_shim_open(struct inode *inode, struct file *file)
 {
 	struct qda_shim_node *snode = container_of(file->private_data, struct qda_shim_node,
@@ -97,6 +196,9 @@ static int qda_shim_open(struct inode *inode, struct file *file)
 		return err;
 	}
 
+	mutex_init(&sf->maps_lock);
+	INIT_LIST_HEAD(&sf->mmap_list);
+	INIT_LIST_HEAD(&sf->mem_map_list);
 	sf->secure_node = snode->secure;
 	sf->secure_channel = snode->sdev->secure_channel;
 	sf->unsigned_support = snode->sdev->unsigned_support;
@@ -110,8 +212,10 @@ static int qda_shim_release(struct inode *inode, struct file *file)
 {
 	struct qda_shim_file *sf = file->private_data;
 
+	shim_mappings_release(sf);
 	qda_legacy_ops_table.release(sf->session);
 	qda_legacy_ops_table.session_destroy(sf->session);
+	mutex_destroy(&sf->maps_lock);
 	kfree(sf);
 
 	return 0;
@@ -191,6 +295,156 @@ static long qda_shim_init_create(struct qda_shim_file *sf, void __user *argp)
 	return err;
 }
 
+static long qda_shim_mmap(struct qda_shim_file *sf, void __user *argp)
+{
+	struct qda_shim_mapping *m;
+	struct fastrpc_req_mmap args;
+	struct drm_gem_object *obj;
+	int err;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	obj = shim_import_fd(sf->session, args.fd);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+	if (!obj)
+		return -EINVAL;
+
+	err = qda_legacy_ops_table.mmap(sf->session, obj, args.vaddrin, args.size, args.flags,
+					&args.vaddrout);
+	if (err) {
+		drm_gem_object_put(obj);
+		return err;
+	}
+
+	m = shim_mapping_add(sf, &sf->mmap_list, args.vaddrout, args.vaddrout, args.size, obj);
+	if (IS_ERR(m)) {
+		err = PTR_ERR(m);
+		goto err_unmap;
+	}
+
+	if (copy_to_user(argp, &args, sizeof(args))) {
+		m = shim_mapping_detach(sf, &sf->mmap_list, args.vaddrout, args.vaddrout);
+		if (m) {
+			qda_legacy_ops_table.munmap(sf->session, m->vaddr, m->size);
+			shim_mapping_free(m);
+		}
+		return -EFAULT;
+	}
+
+	return 0;
+
+err_unmap:
+	qda_legacy_ops_table.munmap(sf->session, args.vaddrout, args.size);
+	drm_gem_object_put(obj);
+
+	return err;
+}
+
+static long qda_shim_munmap(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_req_munmap args;
+	struct qda_shim_mapping *m;
+	int err;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	m = shim_mapping_detach(sf, &sf->mmap_list, args.vaddrout, args.vaddrout);
+	if (!m)
+		return -ENOENT;
+
+	if (m->size != args.size) {
+		shim_mapping_attach(sf, &sf->mmap_list, m);
+		return -EINVAL;
+	}
+
+	err = qda_legacy_ops_table.munmap(sf->session, m->vaddr, m->size);
+	if (err) {
+		shim_mapping_attach(sf, &sf->mmap_list, m);
+		return err;
+	}
+
+	shim_mapping_free(m);
+
+	return 0;
+}
+
+static long qda_shim_mem_map(struct qda_shim_file *sf, void __user *argp)
+{
+	struct qda_shim_mapping *m;
+	struct fastrpc_mem_map args;
+	struct drm_gem_object *obj;
+	int err;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	if (args.offset < 0)
+		return -EINVAL;
+
+	obj = shim_import_fd(sf->session, args.fd);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+	if (!obj)
+		return -EINVAL;
+
+	/* Reserved before the DSP is asked, so the same fd cannot be mapped twice */
+	m = shim_mapping_add(sf, &sf->mem_map_list, args.fd, 0, args.length, obj);
+	if (IS_ERR(m)) {
+		drm_gem_object_put(obj);
+		return PTR_ERR(m);
+	}
+
+	err = qda_legacy_ops_table.mem_map(sf->session, obj, args.flags, args.attrs, args.offset,
+					   args.vaddrin, args.length, args.fd, &args.vaddrout);
+	if (err) {
+		m = shim_mapping_detach(sf, &sf->mem_map_list, args.fd, 0);
+		if (m)
+			shim_mapping_free(m);
+		return err;
+	}
+
+	scoped_guard(mutex, &sf->maps_lock)
+		m->vaddr = args.vaddrout;
+
+	if (copy_to_user(argp, &args, sizeof(args))) {
+		m = shim_mapping_detach(sf, &sf->mem_map_list, args.fd, args.vaddrout);
+		if (m) {
+			qda_legacy_ops_table.mem_unmap(sf->session, m->key, m->vaddr, m->size);
+			shim_mapping_free(m);
+		}
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static long qda_shim_mem_unmap(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_mem_unmap args;
+	struct qda_shim_mapping *m;
+	int err;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	m = shim_mapping_detach(sf, &sf->mem_map_list, args.fd, args.vaddr);
+	if (!m)
+		return -ENOENT;
+
+	err = qda_legacy_ops_table.mem_unmap(sf->session, m->key, m->vaddr, m->size);
+	if (err) {
+		shim_mapping_attach(sf, &sf->mem_map_list, m);
+		return err;
+	}
+
+	shim_mapping_free(m);
+
+	return 0;
+}
+
 static long qda_shim_invoke(struct qda_shim_file *sf, void __user *argp)
 {
 	struct qda_legacy_invoke_arg *largs;
@@ -262,6 +516,14 @@ static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void _
 		return qda_shim_alloc_dma_buf(sf, argp);
 	case FASTRPC_IOCTL_INIT_CREATE:
 		return qda_shim_init_create(sf, argp);
+	case FASTRPC_IOCTL_MMAP:
+		return qda_shim_mmap(sf, argp);
+	case FASTRPC_IOCTL_MUNMAP:
+		return qda_shim_munmap(sf, argp);
+	case FASTRPC_IOCTL_MEM_MAP:
+		return qda_shim_mem_map(sf, argp);
+	case FASTRPC_IOCTL_MEM_UNMAP:
+		return qda_shim_mem_unmap(sf, argp);
 	case FASTRPC_IOCTL_INVOKE:
 		return qda_shim_invoke(sf, argp);
 	default:
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 17d1b488b4a9..999afa9e70c2 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -185,42 +185,74 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
 int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
 {
 	struct drm_qda_mem_map *map_req = data;
+	struct qda_mem_map_args args = {
+		.map = *map_req,
+	};
+	int err;
+
+	err = qda_session_mem_map(file_priv->driver_priv, &args);
+	if (err)
+		return err;
 
-	switch (map_req->request) {
+	map_req->vaddrout = args.map.vaddrout;
+
+	return 0;
+}
+
+/**
+ * qda_session_mem_map() - Map a buffer into the DSP's address space
+ * @session: Session to map for
+ * @args: Mapping parameters; @args->map.vaddrout is filled in on success
+ *
+ * For sessions with no handle table to name the buffer by, such as those of
+ * the legacy shim, which supply @args->obj.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_session_mem_map(struct qda_session *session, struct qda_mem_map_args *args)
+{
+	switch (args->map.request) {
 	case QDA_REQUEST_MMAP:
-		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MMAP, data, file_priv->driver_priv);
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MMAP, args, session);
 	case QDA_REQUEST_MEM_MAP:
-		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_MAP, data,
-					  file_priv->driver_priv);
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_MAP, args, session);
 	default:
 		return -EINVAL;
 	}
 }
 
 /**
- * qda_ioctl_munmap() - Remove a buffer's mapping from the DSP's address space
- * @dev: DRM device structure
- * @data: User-space data (struct drm_qda_mem_unmap)
- * @file_priv: DRM file private data
+ * qda_session_mem_unmap() - Remove a buffer's mapping from the DSP's address space
+ * @session: Session the mapping belongs to
+ * @unmap: Which mapping to remove
  *
  * Return: 0 on success, negative error code on failure
  */
-int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
+int qda_session_mem_unmap(struct qda_session *session, struct drm_qda_mem_unmap *unmap)
 {
-	struct drm_qda_mem_unmap *unmap_req = data;
-
-	switch (unmap_req->request) {
+	switch (unmap->request) {
 	case QDA_REQUEST_MUNMAP:
-		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MUNMAP, data,
-					  file_priv->driver_priv);
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MUNMAP, unmap, session);
 	case QDA_REQUEST_MEM_UNMAP:
-		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, data,
-					  file_priv->driver_priv);
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, unmap, session);
 	default:
 		return -EINVAL;
 	}
 }
 
+/**
+ * qda_ioctl_munmap() - Remove a buffer's mapping from the DSP's address space
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_mem_unmap)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	return qda_session_mem_unmap(file_priv->driver_priv, data);
+}
+
 /**
  * qda_ioctl_invoke() - Run a method on the DSP
  * @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 9c053349c3fa..9b868189df0a 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -8,7 +8,9 @@
 
 #include "qda_drv.h"
 
+struct drm_qda_mem_unmap;
 struct qda_init_create_args;
+struct qda_mem_map_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);
@@ -18,5 +20,7 @@ int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *f
 int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_munmap(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);
+int qda_session_mem_map(struct qda_session *session, struct qda_mem_map_args *args);
+int qda_session_mem_unmap(struct qda_session *session, struct drm_qda_mem_unmap *unmap);
 
 #endif /* __QDA_IOCTL_H__ */
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index 2b3b3f4fb246..ea815ab0c52a 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -75,6 +75,79 @@ static int legacy_release(struct qda_session *session)
 	return qda_release_dsp_process(session->qda_dev, session);
 }
 
+static int legacy_mmap(struct qda_session *session, struct drm_gem_object *obj,
+		       u64 vaddrin, u64 size, u32 flags, u64 *vaddrout)
+{
+	struct qda_mem_map_args args = {
+		.map = {
+			.request = QDA_REQUEST_MMAP,
+			.flags = flags,
+			.vaddrin = vaddrin,
+			.size = size,
+		},
+		.obj = obj,
+	};
+	int err;
+
+	err = qda_session_mem_map(session, &args);
+	if (err)
+		return err;
+
+	*vaddrout = args.map.vaddrout;
+
+	return 0;
+}
+
+static int legacy_mem_map(struct qda_session *session, struct drm_gem_object *obj, u32 flags,
+			  u32 attrs, u32 offset, u64 vaddrin, u64 size, s32 dsp_handle,
+			  u64 *vaddrout)
+{
+	struct qda_mem_map_args args = {
+		.map = {
+			.request = QDA_REQUEST_MEM_MAP,
+			.flags = flags,
+			.attrs = attrs,
+			.offset = offset,
+			.dsp_handle = dsp_handle,
+			.vaddrin = vaddrin,
+			.size = size,
+		},
+		.obj = obj,
+	};
+	int err;
+
+	err = qda_session_mem_map(session, &args);
+	if (err)
+		return err;
+
+	*vaddrout = args.map.vaddrout;
+
+	return 0;
+}
+
+static int legacy_munmap(struct qda_session *session, u64 vaddrout, u64 size)
+{
+	struct drm_qda_mem_unmap unmap = {
+		.request = QDA_REQUEST_MUNMAP,
+		.vaddrout = vaddrout,
+		.size = size,
+	};
+
+	return qda_session_mem_unmap(session, &unmap);
+}
+
+static int legacy_mem_unmap(struct qda_session *session, s32 dsp_handle, u64 vaddr, u64 size)
+{
+	struct drm_qda_mem_unmap unmap = {
+		.request = QDA_REQUEST_MEM_UNMAP,
+		.dsp_handle = dsp_handle,
+		.vaddr = vaddr,
+		.size = size,
+	};
+
+	return qda_session_mem_unmap(session, &unmap);
+}
+
 /*
  * 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
@@ -120,6 +193,10 @@ const struct qda_legacy_ops qda_legacy_ops_table = {
 	.session_destroy = legacy_session_destroy,
 	.init_create = legacy_init_create,
 	.release = legacy_release,
+	.mmap = legacy_mmap,
+	.mem_map = legacy_mem_map,
+	.munmap = legacy_munmap,
+	.mem_unmap = legacy_mem_unmap,
 	.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 05bd1f806c9a..e3126ac10507 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -35,6 +35,17 @@ struct qda_legacy_ops {
 			   u32 filelen, u32 attrs, u32 siglen);
 	/** @release: Release the session's DSP process, if it has one */
 	int (*release)(struct qda_session *session);
+	/** @mmap: Map a buffer into the DSP's address space */
+	int (*mmap)(struct qda_session *session, struct drm_gem_object *obj, u64 vaddrin,
+		    u64 size, u32 flags, u64 *vaddrout);
+	/** @mem_map: Map a region of a buffer with explicit SMMU attributes */
+	int (*mem_map)(struct qda_session *session, struct drm_gem_object *obj, u32 flags,
+		       u32 attrs, u32 offset, u64 vaddrin, u64 size, s32 dsp_handle,
+		       u64 *vaddrout);
+	/** @munmap: Undo a mapping made with @mmap */
+	int (*munmap)(struct qda_session *session, u64 vaddrout, u64 size);
+	/** @mem_unmap: Undo a mapping made with @mem_map */
+	int (*mem_unmap)(struct qda_session *session, s32 dsp_handle, u64 vaddr, u64 size);
 	/** @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


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

* [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (18 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 22:49   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
                   ` (5 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Some daemons need to reach a protection domain that already exists on
the DSP, rather than create a new user process with
DRM_IOCTL_QDA_REMOTE_SESSION_CREATE. Add an ioctl that attaches the
calling session to one, directing every later request on that session
at the attached domain.

Root and Sensors PD attach send the same message to the DSP, which
tells them apart by the domain the request is directed at. The session
only switches domain once the DSP has accepted the attach, and attaching
is refused once a process has been created on the session.

Attaching requires CAP_SYS_ADMIN.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |  1 +
 drivers/accel/qda/qda_fastrpc.c | 13 +++++++++----
 drivers/accel/qda/qda_fastrpc.h |  2 ++
 drivers/accel/qda/qda_ioctl.c   | 36 ++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.h   |  1 +
 include/uapi/drm/qda_accel.h    | 25 +++++++++++++++++++++++++
 6 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index c5b41b869692..1e72dce769b9 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -127,6 +127,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_CREATE, qda_ioctl_init_create, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MMAP, qda_ioctl_mmap, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MUNMAP, qda_ioctl_munmap, 0),
+	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_ATTACH, qda_ioctl_attach, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index b35604fce630..5bf55b7953c8 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -819,15 +819,15 @@ static int qda_fastrpc_use_elf_obj(struct qda_fastrpc_invoke_ctx *ctx,
 }
 
 /*
- * INIT_RELEASE packed layout:
+ * INIT_RELEASE and INIT_ATTACH packed layout:
  *   [0] remote_session_id	sizeof(int)	IN
  */
-static int qda_fastrpc_prepare_args_release_process(struct qda_fastrpc_invoke_ctx *ctx)
+static int qda_fastrpc_prepare_args_session(struct qda_fastrpc_invoke_ctx *ctx, u32 rmid)
 {
 	struct qda_gem_obj *kernel_gem;
 	int err;
 
-	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_RELEASE, 1, 0);
+	ctx->sc = QDA_FASTRPC_SCALARS(rmid, 1, 0);
 	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
 
 	err = alloc_kernel_args(ctx, 1);
@@ -1211,7 +1211,12 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 
 	switch (ctx->type) {
 	case QDA_FASTRPC_RMID_INIT_RELEASE:
-		err = qda_fastrpc_prepare_args_release_process(ctx);
+		err = qda_fastrpc_prepare_args_session(ctx, QDA_FASTRPC_RMID_INIT_RELEASE);
+		break;
+	case QDA_FASTRPC_RMID_INIT_ATTACH:
+		/* The DSP tells the domains apart by the one the request is directed at */
+		ctx->pd = *(int *)argp;
+		err = qda_fastrpc_prepare_args_session(ctx, QDA_FASTRPC_RMID_INIT_ATTACH);
 		break;
 	case QDA_FASTRPC_RMID_INIT_CREATE:
 		/* The new process is in the user PD, and so is the request creating it */
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 10a99bbfa383..29e1c3946d60 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -44,6 +44,7 @@ struct qda_session;
 #define QDA_FASTRPC_MAX_CRCLIST		64	/* CRC list entries */
 
 /* Methods of the initialisation handle */
+#define QDA_FASTRPC_RMID_INIT_ATTACH		0
 #define QDA_FASTRPC_RMID_INIT_RELEASE		1
 #define QDA_FASTRPC_RMID_INIT_MMAP		4
 #define QDA_FASTRPC_RMID_INIT_MUNMAP		5
@@ -59,6 +60,7 @@ struct qda_session;
 /* Protection domains a request can be directed at */
 #define QDA_ROOT_PD			0
 #define QDA_USER_PD			1
+#define QDA_SENSORS_PD			2
 
 /* Largest ELF image a process may be created from */
 #define QDA_FASTRPC_INIT_FILELEN_MAX	(4 * 1024 * 1024)
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 999afa9e70c2..9b8cccfea1e3 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -121,6 +121,8 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
 	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
 		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
 		WRITE_ONCE(session->pd, QDA_USER_PD);
+	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH) {
+		WRITE_ONCE(session->pd, ctx->pd);
 	}
 
 out_put:
@@ -174,6 +176,40 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
 	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
 }
 
+/**
+ * qda_ioctl_attach() - Attach to a DSP-resident protection domain
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_remote_attach)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_remote_attach *args = data;
+	struct qda_session *session = file_priv->driver_priv;
+	int pd;
+
+	if (args->pad)
+		return -EINVAL;
+
+	switch (args->pd_type) {
+	case QDA_ATTACH_ROOT_PD:
+		pd = QDA_ROOT_PD;
+		break;
+	case QDA_ATTACH_SENSORS_PD:
+		pd = QDA_SENSORS_PD;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (READ_ONCE(session->init_mem_gem_obj))
+		return -EBUSY;
+
+	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
+}
+
 /**
  * qda_ioctl_mmap() - Map a buffer into the DSP's address space
  * @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 9b868189df0a..f6f5d64e36ce 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -19,6 +19,7 @@ int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_p
 int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_ioctl_attach(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);
 int qda_session_mem_map(struct qda_session *session, struct qda_mem_map_args *args);
 int qda_session_mem_unmap(struct qda_session *session, struct drm_qda_mem_unmap *unmap);
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 43598ddef461..59ad5b8cc997 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -19,6 +19,7 @@ extern "C" {
 #define DRM_QDA_REMOTE_SESSION_CREATE	0x04
 #define DRM_QDA_REMOTE_MMAP		0x05
 #define DRM_QDA_REMOTE_MUNMAP		0x06
+#define DRM_QDA_REMOTE_SESSION_ATTACH	0x07
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -35,6 +36,9 @@ extern "C" {
 					 struct drm_qda_mem_map)
 #define DRM_IOCTL_QDA_REMOTE_MUNMAP	DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_MUNMAP, \
 					 struct drm_qda_mem_unmap)
+#define DRM_IOCTL_QDA_REMOTE_SESSION_ATTACH					\
+	DRM_IOW(DRM_COMMAND_BASE + DRM_QDA_REMOTE_SESSION_ATTACH,		\
+		struct drm_qda_remote_attach)
 
 /* Request types for drm_qda_mem_map.request */
 #define QDA_REQUEST_MMAP	1	/* Map a whole buffer */
@@ -203,6 +207,27 @@ struct drm_qda_mem_unmap {
 	__u64 size;
 };
 
+/* Protection domains for drm_qda_remote_attach.pd_type */
+#define QDA_ATTACH_ROOT_PD	1
+#define QDA_ATTACH_SENSORS_PD	2
+
+/**
+ * struct drm_qda_remote_attach - Parameters for attaching to a DSP protection domain
+ * @pd_type: QDA_ATTACH_ROOT_PD or QDA_ATTACH_SENSORS_PD
+ * @pad: Must be zero
+ *
+ * Binds this file to a protection domain that already exists on the DSP,
+ * instead of creating one with DRM_IOCTL_QDA_REMOTE_SESSION_CREATE. Every
+ * later request on the file is directed at it. Fails with EBUSY if a process
+ * has already been created on the file.
+ *
+ * Requires CAP_SYS_ADMIN.
+ */
+struct drm_qda_remote_attach {
+	__u32 pd_type;
+	__u32 pad;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (19 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 23:00   ` sashiko-bot
                     ` (2 more replies)
  2026-11-01 22:21 ` [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim Ekansh Gupta
                   ` (4 subsequent siblings)
  25 siblings, 3 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Attaching to the Audio PD differs from Root and Sensors PD attach in two
ways: it names a static PD by a string rather than a fixed protection
domain value, and the DSP expects a region of memory reserved for it in
the device tree to be handed over the first time any session attaches
to it.

Parse the "qcom,vmids" property into a VM permission table and the
reserved-memory region into a physical range at probe time, and use
qcom_scm_assign_mem() to grant the DSP's VMs access to that range. Only
the first successful attach on a device sends the region to the DSP;
later attaches, including after a daemon restart, reuse it. The region
is handed back to HLOS when the rpmsg channel goes away.

The region is optional; without it an Audio PD attach fails.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/Kconfig       |  1 +
 drivers/accel/qda/qda_drv.h     | 19 +++++++++
 drivers/accel/qda/qda_fastrpc.c | 54 ++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h | 30 ++++++++++++++
 drivers/accel/qda/qda_ioctl.c   | 72 ++++++++++++++++++++++++++------
 drivers/accel/qda/qda_rpmsg.c   | 92 ++++++++++++++++++++++++++++++++++++++++-
 include/uapi/drm/qda_accel.h    | 15 +++++--
 7 files changed, 267 insertions(+), 16 deletions(-)

diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
index c440191a54ff..72b5ee6d58ec 100644
--- a/drivers/accel/qda/Kconfig
+++ b/drivers/accel/qda/Kconfig
@@ -9,6 +9,7 @@ config DRM_ACCEL_QDA
 	depends on ARCH_QCOM || COMPILE_TEST
 	depends on RPMSG
 	select AUXILIARY_BUS
+	select QCOM_SCM
 	help
 	  Enables the DRM-based accelerator driver for Qualcomm's Hexagon DSPs.
 	  This driver provides a standardized interface for offloading computational
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 2f98fd83fdda..1d083ea9fdd7 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -9,6 +9,7 @@
 #include <linux/atomic.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/device.h>
+#include <linux/firmware/qcom/qcom_scm.h>
 #include <linux/kref.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
@@ -31,6 +32,9 @@
  */
 #define QDA_LEGACY_AUXDEV_NAME "fastrpc"
 
+/* Upper bound on the "qcom,vmids" DT property */
+#define QDA_MAX_VMIDS	16
+
 /**
  * struct qda_session - One user of a DSP
  *
@@ -118,6 +122,21 @@ struct qda_dev {
 	 * to, or NULL if it could not be created
 	 */
 	struct auxiliary_device *legacy_auxdev;
+	/** @vmperms: VMs the remote heap is shared with, from qcom,vmids */
+	struct qcom_scm_vmperm vmperms[QDA_MAX_VMIDS];
+	/** @vmcount: Number of valid entries in @vmperms */
+	int vmcount;
+	/** @remote_heap_addr: Physical base of the reserved region, or 0 */
+	phys_addr_t remote_heap_addr;
+	/** @remote_heap_size: Size of the reserved region, or 0 */
+	u64 remote_heap_size;
+	/**
+	 * @remote_heap_lock: Held across an Audio PD attach, so the heap is
+	 * handed to the DSP by exactly one of them. Protects @audio_init_mem.
+	 */
+	struct mutex remote_heap_lock;
+	/** @audio_init_mem: True once the DSP has been given the remote heap */
+	bool audio_init_mem;
 };
 
 /**
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 5bf55b7953c8..e9688e3ba404 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -930,6 +930,56 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
 	return err;
 }
 
+/*
+ * INIT_CREATE_STATIC packed layout:
+ *   [0] inbuf	sizeof(qda_fastrpc_create_static_inbuf)	IN
+ *   [1] name	namelen, including the NUL		IN
+ *   [2] heap	sizeof(qda_phy_page)			IN
+ *
+ * The heap descriptor is always present; its size is zero, and so pageslen,
+ * when the DSP already has the remote heap.
+ */
+static int qda_fastrpc_prepare_args_create_static(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+	struct qda_static_pd_args *sargs = argp;
+	struct qda_fastrpc_create_static_inbuf *inbuf;
+	struct qda_gem_obj *kernel_gem;
+	u8 *gv;
+	int err;
+
+	const size_t off_inbuf = 0;
+	const size_t off_name = off_inbuf + sizeof(*inbuf);
+	const size_t off_heap = ALIGN(off_name + sargs->namelen, sizeof(u64));
+	const size_t total_size = off_heap + sizeof(struct qda_phy_page);
+
+	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_CREATE_STATIC, 3, 0);
+	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
+
+	err = alloc_kernel_args(ctx, 3);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, total_size);
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+	gv = kernel_gem->virt;
+
+	inbuf = (struct qda_fastrpc_create_static_inbuf *)(gv + off_inbuf);
+	inbuf->remote_session_id = ctx->session->remote_session_id;
+	inbuf->namelen = sargs->namelen;
+	inbuf->pageslen = sargs->heap.size ? 1 : 0;
+
+	memcpy(gv + off_name, sargs->name, sargs->namelen);
+	memcpy(gv + off_heap, &sargs->heap, sizeof(sargs->heap));
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, gv + off_inbuf, sizeof(*inbuf));
+	assign_kernel_gem_arg(ctx, 1, kernel_gem, gv + off_name, sargs->namelen);
+	assign_kernel_gem_arg(ctx, 2, kernel_gem, gv + off_heap, sizeof(struct qda_phy_page));
+	drm_gem_object_put(&kernel_gem->base);
+
+	return 0;
+}
+
 /*
  * Find the buffer a mapping request names and describe it to the DSP. The
  * buffer is held by the context until the request is done, so that closing its
@@ -1223,6 +1273,10 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 		ctx->pd = QDA_USER_PD;
 		err = qda_fastrpc_prepare_args_init_create(ctx, argp);
 		break;
+	case QDA_FASTRPC_RMID_INIT_CREATE_STATIC:
+		ctx->pd = QDA_USER_PD;
+		err = qda_fastrpc_prepare_args_create_static(ctx, argp);
+		break;
 	case QDA_FASTRPC_RMID_INIT_MMAP:
 		err = qda_fastrpc_prepare_args_map(ctx, argp);
 		break;
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 29e1c3946d60..de04cadcaeb0 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -50,6 +50,7 @@ struct qda_session;
 #define QDA_FASTRPC_RMID_INIT_MUNMAP		5
 #define QDA_FASTRPC_RMID_INIT_CREATE		6
 #define QDA_FASTRPC_RMID_INIT_CREATE_ATTR	7
+#define QDA_FASTRPC_RMID_INIT_CREATE_STATIC	8
 #define QDA_FASTRPC_RMID_INIT_MEM_MAP		10
 #define QDA_FASTRPC_RMID_INIT_MEM_UNMAP		11
 #define QDA_FASTRPC_RMID_INVOKE_DYNAMIC		0xFFFFFFFFU
@@ -65,6 +66,9 @@ struct qda_session;
 /* Largest ELF image a process may be created from */
 #define QDA_FASTRPC_INIT_FILELEN_MAX	(4 * 1024 * 1024)
 
+/* Longest static PD name, including the NUL */
+#define QDA_FASTRPC_INIT_NAMELEN_MAX	128
+
 /* Idle invoke header buffers a session keeps for reuse */
 #define QDA_MSG_BUF_POOL_MAX		8
 
@@ -155,6 +159,20 @@ struct qda_fastrpc_create_process_inbuf {
 	u32 siglen;
 };
 
+/**
+ * struct qda_fastrpc_create_static_inbuf - Input buffer for attaching to a static PD
+ *
+ * Layout the DSP expects; do not reorder.
+ */
+struct qda_fastrpc_create_static_inbuf {
+	/** @remote_session_id: Session the attach belongs to */
+	int remote_session_id;
+	/** @namelen: Length of the static PD name including the NUL */
+	u32 namelen;
+	/** @pageslen: Number of remote heap descriptors that follow the name (0 or 1) */
+	u32 pageslen;
+};
+
 /**
  * struct qda_fastrpc_map_req_msg - Request to map a whole buffer
  *
@@ -379,6 +397,18 @@ struct qda_mem_map_args {
 	struct drm_gem_object *obj;
 };
 
+/**
+ * struct qda_static_pd_args - Kernel-side arguments for attaching to a static PD
+ */
+struct qda_static_pd_args {
+	/** @name: Kernel copy of the NUL-terminated PD name */
+	const char *name;
+	/** @namelen: Length of @name including the NUL */
+	u32 namelen;
+	/** @heap: Remote heap to hand the DSP, or zero size if it already has it */
+	struct qda_phy_page heap;
+};
+
 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_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 9b8cccfea1e3..41234fc576a9 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -121,7 +121,8 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
 	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
 		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
 		WRITE_ONCE(session->pd, QDA_USER_PD);
-	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH) {
+	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH ||
+		   type == QDA_FASTRPC_RMID_INIT_CREATE_STATIC) {
 		WRITE_ONCE(session->pd, ctx->pd);
 	}
 
@@ -176,6 +177,54 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
 	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
 }
 
+/*
+ * The Audio PD keeps the remote heap across daemon restarts, so only the first
+ * attach on a device hands it to the DSP. The lock is held across the attach
+ * so that a concurrent one cannot skip the heap while the first is still in
+ * flight, and the flag is set only once the DSP has accepted it.
+ */
+static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remote_attach *args)
+{
+	struct qda_dev *qdev = session->qda_dev;
+	struct qda_static_pd_args sargs = {};
+	char *name;
+	int err;
+
+	if (strcmp(qdev->dsp_name, "adsp") || !qdev->remote_heap_size)
+		return -ENODEV;
+
+	if (!args->namelen || args->namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
+		return -EINVAL;
+
+	name = memdup_user(u64_to_user_ptr(args->name), args->namelen);
+	if (IS_ERR(name))
+		return PTR_ERR(name);
+
+	if (name[args->namelen - 1]) {
+		err = -EINVAL;
+		goto out_free;
+	}
+
+	sargs.name = name;
+	sargs.namelen = args->namelen;
+
+	scoped_guard(mutex, &qdev->remote_heap_lock) {
+		if (!qdev->audio_init_mem) {
+			sargs.heap.addr = qdev->remote_heap_addr;
+			sargs.heap.size = qdev->remote_heap_size;
+		}
+
+		err = qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE_STATIC, &sargs, session);
+		if (!err && sargs.heap.size)
+			qdev->audio_init_mem = true;
+	}
+
+out_free:
+	kfree(name);
+
+	return err;
+}
+
 /**
  * qda_ioctl_attach() - Attach to a DSP-resident protection domain
  * @dev: DRM device structure
@@ -190,24 +239,23 @@ int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_p
 	struct qda_session *session = file_priv->driver_priv;
 	int pd;
 
-	if (args->pad)
-		return -EINVAL;
+	if (READ_ONCE(session->init_mem_gem_obj))
+		return -EBUSY;
 
 	switch (args->pd_type) {
 	case QDA_ATTACH_ROOT_PD:
-		pd = QDA_ROOT_PD;
-		break;
 	case QDA_ATTACH_SENSORS_PD:
-		pd = QDA_SENSORS_PD;
-		break;
+		if (args->name || args->namelen)
+			return -EINVAL;
+
+		pd = args->pd_type == QDA_ATTACH_ROOT_PD ? QDA_ROOT_PD : QDA_SENSORS_PD;
+
+		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
+	case QDA_ATTACH_AUDIO_PD:
+		return qda_attach_audio_pd(session, args);
 	default:
 		return -EINVAL;
 	}
-
-	if (READ_ONCE(session->init_mem_gem_obj))
-		return -EBUSY;
-
-	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
 }
 
 /**
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 7e58df3eb2e1..b93e906b5793 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -4,7 +4,9 @@
 #include <linux/completion.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_reserved_mem.h>
 #include <linux/rpmsg.h>
+#include <linux/string.h>
 #include <linux/wait.h>
 #include <drm/drm_print.h>
 
@@ -145,6 +147,81 @@ static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
 	return ret;
 }
 
+static int qda_init_vmperms(struct qda_dev *qdev)
+{
+	u32 vmids[QDA_MAX_VMIDS];
+	int vmcount, i;
+
+	vmcount = of_property_read_variable_u32_array(qdev->dev->of_node, "qcom,vmids",
+						      vmids, 0, QDA_MAX_VMIDS);
+	if (vmcount == -EINVAL || vmcount == -ENODATA)
+		return 0;
+	if (vmcount < 0)
+		return vmcount;
+
+	if (!qcom_scm_is_available())
+		return -EPROBE_DEFER;
+
+	for (i = 0; i < vmcount; i++) {
+		qdev->vmperms[i].vmid = vmids[i];
+		qdev->vmperms[i].perm = QCOM_SCM_PERM_RWX;
+	}
+	qdev->vmcount = vmcount;
+
+	return 0;
+}
+
+/*
+ * The region is optional: without it an Audio PD attach is refused. It is
+ * recorded only once it has been assigned, so teardown hands back exactly
+ * what was handed over.
+ */
+static int qda_init_remote_heap(struct qda_dev *qdev)
+{
+	u64 src_perms = BIT(QCOM_SCM_VMID_HLOS);
+	struct resource res;
+	int ret;
+
+	if (strcmp(qdev->dsp_name, "adsp") && strcmp(qdev->dsp_name, "sdsp"))
+		return 0;
+
+	if (of_reserved_mem_region_to_resource(qdev->dev->of_node, 0, &res))
+		return 0;
+
+	if (qdev->vmcount) {
+		ret = qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms,
+					  qdev->vmperms, qdev->vmcount);
+		if (ret)
+			return ret;
+	}
+
+	qdev->remote_heap_addr = res.start;
+	qdev->remote_heap_size = resource_size(&res);
+
+	return 0;
+}
+
+static void qda_deinit_remote_heap(struct qda_dev *qdev)
+{
+	struct qcom_scm_vmperm dst_perms = {
+		.vmid = QCOM_SCM_VMID_HLOS,
+		.perm = QCOM_SCM_PERM_RWX,
+	};
+	u64 src_perms = 0;
+	int ret, i;
+
+	if (!qdev->remote_heap_size || !qdev->vmcount)
+		return;
+
+	for (i = 0; i < qdev->vmcount; i++)
+		src_perms |= BIT(qdev->vmperms[i].vmid);
+
+	ret = qcom_scm_assign_mem(qdev->remote_heap_addr, qdev->remote_heap_size,
+				  &src_perms, &dst_perms, 1);
+	if (ret)
+		dev_err(qdev->dev, "Failed to assign remote heap back to HLOS: %d\n", ret);
+}
+
 static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
 {
 	struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
@@ -163,6 +240,7 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
 	qda_fastrpc_flush_pending(qdev);
 	qda_cb_unpopulate(qdev);
 	qda_deinit_device(qdev);
+	qda_deinit_remote_heap(qdev);
 }
 
 static int qda_count_cb_nodes(struct device_node *parent_node)
@@ -195,10 +273,20 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
 		return ret;
 	}
 
-	ret = qda_init_device(qdev, qda_count_cb_nodes(rpdev->dev.of_node));
+	mutex_init(&qdev->remote_heap_lock);
+
+	ret = qda_init_vmperms(qdev);
+	if (ret)
+		return ret;
+
+	ret = qda_init_remote_heap(qdev);
 	if (ret)
 		return ret;
 
+	ret = qda_init_device(qdev, qda_count_cb_nodes(rpdev->dev.of_node));
+	if (ret)
+		goto err_deinit_remote_heap;
+
 	ret = qda_cb_populate(qdev, rpdev->dev.of_node);
 	if (ret)
 		goto err_deinit;
@@ -213,6 +301,8 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
 	qda_cb_unpopulate(qdev);
 err_deinit:
 	qda_deinit_device(qdev);
+err_deinit_remote_heap:
+	qda_deinit_remote_heap(qdev);
 	return ret;
 }
 
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 59ad5b8cc997..7c439058876e 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -210,22 +210,31 @@ struct drm_qda_mem_unmap {
 /* Protection domains for drm_qda_remote_attach.pd_type */
 #define QDA_ATTACH_ROOT_PD	1
 #define QDA_ATTACH_SENSORS_PD	2
+#define QDA_ATTACH_AUDIO_PD	3
 
 /**
  * struct drm_qda_remote_attach - Parameters for attaching to a DSP protection domain
- * @pd_type: QDA_ATTACH_ROOT_PD or QDA_ATTACH_SENSORS_PD
- * @pad: Must be zero
+ * @pd_type: QDA_ATTACH_ROOT_PD, QDA_ATTACH_SENSORS_PD or QDA_ATTACH_AUDIO_PD
+ * @namelen: Length of the name in bytes including the NUL terminator
+ *           (QDA_ATTACH_AUDIO_PD only, otherwise must be zero)
+ * @name: User pointer to the NUL-terminated name of the static PD to attach
+ *        to (QDA_ATTACH_AUDIO_PD only, otherwise must be zero)
  *
  * Binds this file to a protection domain that already exists on the DSP,
  * instead of creating one with DRM_IOCTL_QDA_REMOTE_SESSION_CREATE. Every
  * later request on the file is directed at it. Fails with EBUSY if a process
  * has already been created on the file.
  *
+ * For QDA_ATTACH_AUDIO_PD, the first successful attach on a device also
+ * hands the DSP the remote heap reserved for it in the device tree; later
+ * attaches reuse that heap.
+ *
  * Requires CAP_SYS_ADMIN.
  */
 struct drm_qda_remote_attach {
 	__u32 pd_type;
-	__u32 pad;
+	__u32 namelen;
+	__u64 name;
 };
 
 #if defined(__cplusplus)

-- 
2.34.1


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

* [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (20 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 23:00   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 23/25] accel/qda: Add DSP capability query Ekansh Gupta
                   ` (3 subsequent siblings)
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Wire FASTRPC_IOCTL_INIT_ATTACH, FASTRPC_IOCTL_INIT_ATTACH_SNS and
FASTRPC_IOCTL_INIT_CREATE_STATIC into the legacy FastRPC compatibility
shim, so existing user space using the /dev/fastrpc-* ioctl interface can
attach to the Root, Sensors and Audio protection domains.

Root and Sensors PD attach carry no payload. INIT_CREATE_STATIC forwards
its name and length; memlen is ignored, since the Audio PD remote heap
size always comes from the device tree rather than from user space.

Move the attach logic out of the ioctl handler into a session-level
function that both interfaces call. It checks CAP_SYS_ADMIN itself, so
the legacy path is subject to the same privilege requirement as the
native one.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_fastrpc_shim.c | 24 ++++++++++++++++
 drivers/accel/qda/qda_ioctl.c        | 54 +++++++++++++++++++++++++-----------
 drivers/accel/qda/qda_ioctl.h        |  2 ++
 drivers/accel/qda/qda_legacy_ops.c   |  1 +
 drivers/accel/qda/qda_legacy_ops.h   |  3 ++
 5 files changed, 68 insertions(+), 16 deletions(-)

diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
index c46d2de4f581..c1464b1adbc2 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -18,6 +18,7 @@
 #include <linux/uaccess.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_gem.h>
+#include <drm/qda_accel.h>
 #include <uapi/misc/fastrpc.h>
 #include "qda_drv.h"
 #include "qda_fastrpc.h"
@@ -295,6 +296,23 @@ static long qda_shim_init_create(struct qda_shim_file *sf, void __user *argp)
 	return err;
 }
 
+static long qda_shim_attach(struct qda_shim_file *sf, u32 pd_type)
+{
+	return qda_legacy_ops_table.attach(sf->session, pd_type, NULL, 0);
+}
+
+/* memlen is ignored: the Audio PD heap size comes from the device tree */
+static long qda_shim_init_create_static(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_init_create_static args;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	return qda_legacy_ops_table.attach(sf->session, QDA_ATTACH_AUDIO_PD,
+					   u64_to_user_ptr(args.name), args.namelen);
+}
+
 static long qda_shim_mmap(struct qda_shim_file *sf, void __user *argp)
 {
 	struct qda_shim_mapping *m;
@@ -516,6 +534,12 @@ static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void _
 		return qda_shim_alloc_dma_buf(sf, argp);
 	case FASTRPC_IOCTL_INIT_CREATE:
 		return qda_shim_init_create(sf, argp);
+	case FASTRPC_IOCTL_INIT_ATTACH:
+		return qda_shim_attach(sf, QDA_ATTACH_ROOT_PD);
+	case FASTRPC_IOCTL_INIT_ATTACH_SNS:
+		return qda_shim_attach(sf, QDA_ATTACH_SENSORS_PD);
+	case FASTRPC_IOCTL_INIT_CREATE_STATIC:
+		return qda_shim_init_create_static(sf, argp);
 	case FASTRPC_IOCTL_MMAP:
 		return qda_shim_mmap(sf, argp);
 	case FASTRPC_IOCTL_MUNMAP:
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 41234fc576a9..aaf3e5e6b5b1 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -183,7 +183,7 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
  * so that a concurrent one cannot skip the heap while the first is still in
  * flight, and the flag is set only once the DSP has accepted it.
  */
-static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remote_attach *args)
+static int qda_attach_audio_pd(struct qda_session *session, const char __user *uname, u32 namelen)
 {
 	struct qda_dev *qdev = session->qda_dev;
 	struct qda_static_pd_args sargs = {};
@@ -193,20 +193,20 @@ static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remot
 	if (strcmp(qdev->dsp_name, "adsp") || !qdev->remote_heap_size)
 		return -ENODEV;
 
-	if (!args->namelen || args->namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
+	if (!namelen || namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
 		return -EINVAL;
 
-	name = memdup_user(u64_to_user_ptr(args->name), args->namelen);
+	name = memdup_user(uname, namelen);
 	if (IS_ERR(name))
 		return PTR_ERR(name);
 
-	if (name[args->namelen - 1]) {
+	if (name[namelen - 1]) {
 		err = -EINVAL;
 		goto out_free;
 	}
 
 	sargs.name = name;
-	sargs.namelen = args->namelen;
+	sargs.namelen = namelen;
 
 	scoped_guard(mutex, &qdev->remote_heap_lock) {
 		if (!qdev->audio_init_mem) {
@@ -226,38 +226,60 @@ static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remot
 }
 
 /**
- * qda_ioctl_attach() - Attach to a DSP-resident protection domain
- * @dev: DRM device structure
- * @data: User-space data (struct drm_qda_remote_attach)
- * @file_priv: DRM file private data
+ * qda_session_attach() - Attach a session to a DSP-resident protection domain
+ * @session: Session to attach
+ * @pd_type: QDA_ATTACH_ROOT_PD, QDA_ATTACH_SENSORS_PD or QDA_ATTACH_AUDIO_PD
+ * @name: User pointer to the static PD name (QDA_ATTACH_AUDIO_PD only)
+ * @namelen: Length of @name including the NUL (QDA_ATTACH_AUDIO_PD only)
+ *
+ * Shared by the accel UAPI and the legacy shim, so the privilege check lives
+ * here rather than on the ioctl.
  *
  * Return: 0 on success, negative error code on failure
  */
-int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv)
+int qda_session_attach(struct qda_session *session, u32 pd_type, const char __user *name,
+		       u32 namelen)
 {
-	struct drm_qda_remote_attach *args = data;
-	struct qda_session *session = file_priv->driver_priv;
 	int pd;
 
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
 	if (READ_ONCE(session->init_mem_gem_obj))
 		return -EBUSY;
 
-	switch (args->pd_type) {
+	switch (pd_type) {
 	case QDA_ATTACH_ROOT_PD:
 	case QDA_ATTACH_SENSORS_PD:
-		if (args->name || args->namelen)
+		if (name || namelen)
 			return -EINVAL;
 
-		pd = args->pd_type == QDA_ATTACH_ROOT_PD ? QDA_ROOT_PD : QDA_SENSORS_PD;
+		pd = pd_type == QDA_ATTACH_ROOT_PD ? QDA_ROOT_PD : QDA_SENSORS_PD;
 
 		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
 	case QDA_ATTACH_AUDIO_PD:
-		return qda_attach_audio_pd(session, args);
+		return qda_attach_audio_pd(session, name, namelen);
 	default:
 		return -EINVAL;
 	}
 }
 
+/**
+ * qda_ioctl_attach() - Attach to a DSP-resident protection domain
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_remote_attach)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_remote_attach *args = data;
+
+	return qda_session_attach(file_priv->driver_priv, args->pd_type,
+				  u64_to_user_ptr(args->name), args->namelen);
+}
+
 /**
  * qda_ioctl_mmap() - Map a buffer into the DSP's address space
  * @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index f6f5d64e36ce..94adba549989 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -21,6 +21,8 @@ int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_pri
 int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_attach(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);
+int qda_session_attach(struct qda_session *session, u32 pd_type, const char __user *name,
+		       u32 namelen);
 int qda_session_mem_map(struct qda_session *session, struct qda_mem_map_args *args);
 int qda_session_mem_unmap(struct qda_session *session, struct drm_qda_mem_unmap *unmap);
 
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index ea815ab0c52a..fc5e94c0c9bc 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -192,6 +192,7 @@ const struct qda_legacy_ops qda_legacy_ops_table = {
 	.session_create = legacy_session_create,
 	.session_destroy = legacy_session_destroy,
 	.init_create = legacy_init_create,
+	.attach = qda_session_attach,
 	.release = legacy_release,
 	.mmap = legacy_mmap,
 	.mem_map = legacy_mem_map,
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
index e3126ac10507..95a169edc594 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -33,6 +33,9 @@ struct qda_legacy_ops {
 	/** @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);
+	/** @attach: Attach the session to a DSP-resident protection domain */
+	int (*attach)(struct qda_session *session, u32 pd_type, const char __user *name,
+		      u32 namelen);
 	/** @release: Release the session's DSP process, if it has one */
 	int (*release)(struct qda_session *session);
 	/** @mmap: Map a buffer into the DSP's address space */

-- 
2.34.1


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

* [PATCH v4 23/25] accel/qda: Add DSP capability query
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (21 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 23:03   ` sashiko-bot
  2026-11-02  8:46   ` Arnd Bergmann
  2026-11-01 22:21 ` [PATCH v4 24/25] accel/qda: Add legacy shim support for " Ekansh Gupta
                   ` (2 subsequent siblings)
  25 siblings, 2 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Add a DRM_IOCTL_QDA_QUERY query type that returns a DSP capability
attribute, such as async support, unsigned PD offload or VTCM size, so
user space can ask the DSP what it supports without a separate ioctl.

The attributes are fetched from the DSP through its stateless utilities
handle, which needs no process context, and cached on the device the
first time any session queries them. Later queries from any session are
served from the cache. A DSP without capability support makes the query
fail with -EOPNOTSUPP.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c     |  2 ++
 drivers/accel/qda/qda_drv.h     |  9 ++++++++
 drivers/accel/qda/qda_fastrpc.c | 47 +++++++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_fastrpc.h | 12 +++++++++++
 drivers/accel/qda/qda_ioctl.c   | 42 ++++++++++++++++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.h   |  2 ++
 include/uapi/drm/qda_accel.h    | 15 +++++++++++--
 7 files changed, 127 insertions(+), 2 deletions(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 1e72dce769b9..51d8ef10c6c1 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -154,6 +154,7 @@ struct qda_dev *qda_alloc_device(struct device *dev)
 
 	INIT_LIST_HEAD(&qdev->cb_devs);
 	mutex_init(&qdev->import_lock);
+	mutex_init(&qdev->attr_lock);
 	xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
 	atomic64_set(&qdev->ctx_seq, 0);
 	atomic_set(&qdev->session_id_seq, 0);
@@ -181,6 +182,7 @@ void qda_deinit_device(struct qda_dev *qdev)
 	qda_memory_manager_exit(qdev->iommu_mgr);
 	xa_destroy(&qdev->ctx_xa);
 	mutex_destroy(&qdev->import_lock);
+	mutex_destroy(&qdev->attr_lock);
 }
 
 int qda_register_device(struct qda_dev *qdev)
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 1d083ea9fdd7..cbb580d03e0b 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -35,6 +35,9 @@
 /* Upper bound on the "qcom,vmids" DT property */
 #define QDA_MAX_VMIDS	16
 
+/* Size of the cached DSP attribute array; index 0 is reserved */
+#define QDA_MAX_DSP_ATTRIBUTES	256
+
 /**
  * struct qda_session - One user of a DSP
  *
@@ -122,6 +125,12 @@ struct qda_dev {
 	 * to, or NULL if it could not be created
 	 */
 	struct auxiliary_device *legacy_auxdev;
+	/** @attr_lock: Serialises the DSP capability query and protects the cache below */
+	struct mutex attr_lock;
+	/** @valid_attributes: True once @dsp_attributes has been queried from the DSP */
+	bool valid_attributes;
+	/** @dsp_attributes: Cached DSP capability attributes */
+	u32 dsp_attributes[QDA_MAX_DSP_ATTRIBUTES];
 	/** @vmperms: VMs the remote heap is shared with, from qcom,vmids */
 	struct qcom_scm_vmperm vmperms[QDA_MAX_VMIDS];
 	/** @vmcount: Number of valid entries in @vmperms */
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index e9688e3ba404..6044cfad53e9 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -638,6 +638,13 @@ void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
 	int idx;
 
 	switch (ctx->type) {
+	case QDA_FASTRPC_TYPE_GET_DSP_INFO:
+		/* @argp is the attribute array; entry 0 is reserved */
+		((u32 *)argp)[0] = 0;
+		memcpy((u32 *)argp + 1,
+		       (u8 *)to_qda_gem_obj(ctx->gem_objs[1])->virt + ctx->args[1].offset,
+		       ctx->args[1].length);
+		return;
 	case QDA_FASTRPC_RMID_INIT_MMAP:
 		idx = 2;
 		break;
@@ -980,6 +987,43 @@ static int qda_fastrpc_prepare_args_create_static(struct qda_fastrpc_invoke_ctx
 	return 0;
 }
 
+/*
+ * DSP capability query packed layout:
+ *   [0] count	sizeof(u32), the capacity of the array below	IN
+ *   [1] attrs	(QDA_MAX_DSP_ATTRIBUTES - 1) * sizeof(u32)	OUT
+ *
+ * Index 0 of the cached array is reserved, so only the rest is exchanged.
+ */
+static int qda_fastrpc_prepare_args_get_dsp_info(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	const u32 attr_count = QDA_MAX_DSP_ATTRIBUTES - 1;
+	struct qda_gem_obj *kernel_gem;
+	int err;
+
+	const size_t off_attrs = sizeof(u32);
+	const size_t total_size = off_attrs + attr_count * sizeof(u32);
+
+	ctx->sc = QDA_FASTRPC_SCALARS(0, 1, 1);
+	ctx->handle = QDA_FASTRPC_DSP_UTILITIES_HANDLE;
+
+	err = alloc_kernel_args(ctx, 2);
+	if (err)
+		return err;
+
+	kernel_gem = alloc_kernel_gem(ctx, total_size);
+	if (IS_ERR(kernel_gem))
+		return PTR_ERR(kernel_gem);
+
+	*(u32 *)kernel_gem->virt = attr_count;
+
+	assign_kernel_gem_arg(ctx, 0, kernel_gem, kernel_gem->virt, sizeof(u32));
+	assign_kernel_gem_arg(ctx, 1, kernel_gem, (u8 *)kernel_gem->virt + off_attrs,
+			      attr_count * sizeof(u32));
+	drm_gem_object_put(&kernel_gem->base);
+
+	return 0;
+}
+
 /*
  * Find the buffer a mapping request names and describe it to the DSP. The
  * buffer is held by the context until the request is done, so that closing its
@@ -1277,6 +1321,9 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
 		ctx->pd = QDA_USER_PD;
 		err = qda_fastrpc_prepare_args_create_static(ctx, argp);
 		break;
+	case QDA_FASTRPC_TYPE_GET_DSP_INFO:
+		err = qda_fastrpc_prepare_args_get_dsp_info(ctx);
+		break;
 	case QDA_FASTRPC_RMID_INIT_MMAP:
 		err = qda_fastrpc_prepare_args_map(ctx, argp);
 		break;
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index de04cadcaeb0..7d5cd04943a3 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -55,9 +55,21 @@ struct qda_session;
 #define QDA_FASTRPC_RMID_INIT_MEM_UNMAP		11
 #define QDA_FASTRPC_RMID_INVOKE_DYNAMIC		0xFFFFFFFFU
 
+/*
+ * ctx->type tag for the DSP capability query. Not a wire method ID: the query
+ * is addressed by QDA_FASTRPC_DSP_UTILITIES_HANDLE, method 0.
+ */
+#define QDA_FASTRPC_TYPE_GET_DSP_INFO		0xFFFFFFFEU
+
 /* Handle the initialisation methods are addressed to */
 #define QDA_FASTRPC_INIT_HANDLE		0x1
 
+/* Handle of the DSP's utility calls, which include the capability query */
+#define QDA_FASTRPC_DSP_UTILITIES_HANDLE	0x2
+
+/* Status the DSP returns when it does not support the capability query */
+#define QDA_FASTRPC_DSP_UNSUPPORTED_API		0x80000414U
+
 /* Protection domains a request can be directed at */
 #define QDA_ROOT_PD			0
 #define QDA_USER_PD			1
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index aaf3e5e6b5b1..c784defd0f70 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -27,6 +27,9 @@ int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_pr
 	case QDA_QUERY_DSP_NAME:
 		strscpy(args->dsp_name, qdev->dsp_name, sizeof(args->dsp_name));
 		return 0;
+	case QDA_QUERY_DSP_CAPABILITIES:
+		return qda_get_dsp_capability(qdev, file_priv->driver_priv,
+					      args->cap.attribute_id, &args->cap.capability);
 	default:
 		return -EINVAL;
 	}
@@ -165,6 +168,45 @@ int qda_session_init_create(struct qda_session *session, struct qda_init_create_
 	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE, create, session);
 }
 
+/**
+ * qda_get_dsp_capability() - Read one DSP capability attribute
+ * @qdev: Device whose DSP to query
+ * @session: Session the query is issued on if the cache is still empty
+ * @attribute_id: Attribute to read
+ * @capability: Receives the value of the attribute
+ *
+ * The attributes are fetched from the DSP once per device and served from the
+ * cache afterwards, to every session. The cache is only written by a query
+ * that succeeded.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_get_dsp_capability(struct qda_dev *qdev, struct qda_session *session,
+			   u32 attribute_id, u32 *capability)
+{
+	int err;
+
+	if (attribute_id >= QDA_MAX_DSP_ATTRIBUTES)
+		return -EOVERFLOW;
+
+	guard(mutex)(&qdev->attr_lock);
+
+	if (!qdev->valid_attributes) {
+		err = qda_fastrpc_invoke(QDA_FASTRPC_TYPE_GET_DSP_INFO, qdev->dsp_attributes,
+					 session);
+		if (err == (int)QDA_FASTRPC_DSP_UNSUPPORTED_API)
+			return -EOPNOTSUPP;
+		if (err)
+			return err;
+
+		qdev->valid_attributes = true;
+	}
+
+	*capability = qdev->dsp_attributes[attribute_id];
+
+	return 0;
+}
+
 /**
  * qda_release_dsp_process() - Tear down a session's DSP process
  * @qdev: QDA device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 94adba549989..329f3809c8ce 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -23,6 +23,8 @@ int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_p
 int qda_session_init_create(struct qda_session *session, struct qda_init_create_args *create);
 int qda_session_attach(struct qda_session *session, u32 pd_type, const char __user *name,
 		       u32 namelen);
+int qda_get_dsp_capability(struct qda_dev *qdev, struct qda_session *session,
+			   u32 attribute_id, u32 *capability);
 int qda_session_mem_map(struct qda_session *session, struct qda_mem_map_args *args);
 int qda_session_mem_unmap(struct qda_session *session, struct drm_qda_mem_unmap *unmap);
 
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 7c439058876e..07b943a3c822 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -50,19 +50,30 @@ extern "C" {
 
 /* Query type definitions for drm_qda_query */
 #define QDA_QUERY_DSP_NAME		1
+#define QDA_QUERY_DSP_CAPABILITIES	2
 
 /**
  * struct drm_qda_query - Device information query structure
  * @query_type: Type of query (input)
  * @dsp_name: Null-terminated name of the DSP (returned when query_type is
  *            QDA_QUERY_DSP_NAME)
+ * @cap: DSP capability query (used when query_type is
+ *       QDA_QUERY_DSP_CAPABILITIES): @cap.attribute_id selects the attribute
+ *       (input) and @cap.capability returns its value (output)
  *
  * This structure is used with DRM_IOCTL_QDA_QUERY to query device attributes
- * based on @query_type.
+ * based on @query_type. @dsp_name and @cap share storage, as only one query
+ * is made per call.
  */
 struct drm_qda_query {
 	__u32 query_type;
-	__u8 dsp_name[16];
+	union {
+		__u8 dsp_name[16];
+		struct {
+			__u32 attribute_id;
+			__u32 capability;
+		} cap;
+	};
 };
 
 /**

-- 
2.34.1


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

* [PATCH v4 24/25] accel/qda: Add legacy shim support for DSP capability query
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (22 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 23/25] accel/qda: Add DSP capability query Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 23:05   ` sashiko-bot
  2026-11-01 22:21 ` [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion Ekansh Gupta
  2026-11-02  8:53 ` [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Krzysztof Kozlowski
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Wire FASTRPC_IOCTL_GET_DSP_INFO into the legacy FastRPC compatibility
shim on top of qda_fastrpc_get_dsp_info(), so existing user space can
query DSP capabilities through the /dev/fastrpc-* interface exactly
as it could with the driver this one replaces.

The device-wide capability cache introduced for the native
DRM_IOCTL_QDA_QUERY path is reused as-is; the shim only has to
translate between the legacy fastrpc_ioctl_capability layout and the
shared qda_fastrpc_get_dsp_info() call.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_fastrpc_shim.c | 20 ++++++++++++++++++++
 drivers/accel/qda/qda_legacy_ops.c   |  6 ++++++
 drivers/accel/qda/qda_legacy_ops.h   |  2 ++
 3 files changed, 28 insertions(+)

diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
index c1464b1adbc2..2be1dd116ef7 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -463,6 +463,24 @@ static long qda_shim_mem_unmap(struct qda_shim_file *sf, void __user *argp)
 	return 0;
 }
 
+static long qda_shim_get_dsp_info(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_ioctl_capability args;
+	int err;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	err = qda_legacy_ops_table.get_dsp_info(sf->session, args.attribute_id, &args.capability);
+	if (err)
+		return err;
+
+	if (copy_to_user(argp, &args, sizeof(args)))
+		return -EFAULT;
+
+	return 0;
+}
+
 static long qda_shim_invoke(struct qda_shim_file *sf, void __user *argp)
 {
 	struct qda_legacy_invoke_arg *largs;
@@ -550,6 +568,8 @@ static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void _
 		return qda_shim_mem_unmap(sf, argp);
 	case FASTRPC_IOCTL_INVOKE:
 		return qda_shim_invoke(sf, argp);
+	case FASTRPC_IOCTL_GET_DSP_INFO:
+		return qda_shim_get_dsp_info(sf, argp);
 	default:
 		/*
 		 * Includes FASTRPC_IOCTL_FREE_DMA_BUFF, which the driver this
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index fc5e94c0c9bc..8d9d597c76c2 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -188,6 +188,11 @@ static int legacy_invoke(struct qda_session *session, u32 handle, u32 sc,
 	return qda_fastrpc_invoke_legacy(session->qda_dev, session, handle, sc, args);
 }
 
+static int legacy_get_dsp_info(struct qda_session *session, u32 attribute_id, u32 *capability)
+{
+	return qda_get_dsp_capability(session->qda_dev, session, attribute_id, capability);
+}
+
 const struct qda_legacy_ops qda_legacy_ops_table = {
 	.session_create = legacy_session_create,
 	.session_destroy = legacy_session_destroy,
@@ -201,5 +206,6 @@ const struct qda_legacy_ops qda_legacy_ops_table = {
 	.alloc_dmabuf = legacy_alloc_dmabuf,
 	.import_dmabuf = legacy_import_dmabuf,
 	.invoke = legacy_invoke,
+	.get_dsp_info = legacy_get_dsp_info,
 };
 EXPORT_SYMBOL_FOR_MODULES(qda_legacy_ops_table, "qda_fastrpc_shim");
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
index 95a169edc594..c79003021fc1 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -57,6 +57,8 @@ struct qda_legacy_ops {
 	/** @invoke: Run a method on the DSP; takes ownership of each @args[i].obj */
 	int (*invoke)(struct qda_session *session, u32 handle, u32 sc,
 		      struct qda_legacy_invoke_arg *args);
+	/** @get_dsp_info: Query a DSP capability attribute */
+	int (*get_dsp_info)(struct qda_session *session, u32 attribute_id, u32 *capability);
 };
 
 extern const struct qda_legacy_ops qda_legacy_ops_table;

-- 
2.34.1


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

* [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (23 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 24/25] accel/qda: Add legacy shim support for " Ekansh Gupta
@ 2026-11-01 22:21 ` Ekansh Gupta
  2026-11-01 23:02   ` sashiko-bot
  2026-11-02  8:53 ` [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Krzysztof Kozlowski
  25 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-01 22:21 UTC (permalink / raw)
  To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Ekansh Gupta, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

From: Ekansh Gupta <ekangupt@qti.qualcomm.com>

Let a session opt in to completion polling for dynamically loaded
module invocations in a user PD via a new DRM_IOCTL_QDA_SET_OPTION
ioctl (and the equivalent legacy FASTRPC_IOCTL_SET_OPTION), mirroring
drivers/misc/fastrpc.c's FASTRPC_POLL_MODE.

A poll word is reserved at the end of every invocation's metadata
region. When polling is enabled, in use, and the invocation targets a
dynamically loaded handle in a user PD, the driver busy-waits on that
word for QDA_FASTRPC_POLL_RESPONSE instead of immediately blocking on
the rpmsg response, trading CPU cycles for lower round-trip latency.
If the DSP has not answered by the time the poll loop times out, the
driver falls back to waiting for the interrupt-based rpmsg response as
usual; the two paths share an is_work_done flag so whichever notices
completion first wins without racing the other.

Only SoCs whose DSP firmware actually implements this are allowed to
enable it: qda_soc_data gets a poll_mode_supported flag, set for
Kaanapali, and a closed list of older platforms using the generic
default_soc_data is checked as a fallback.

Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>
---
 drivers/accel/qda/qda_drv.c          |  1 +
 drivers/accel/qda/qda_drv.h          |  8 +++++
 drivers/accel/qda/qda_fastrpc.c      | 67 +++++++++++++++++++++++++++++++++---
 drivers/accel/qda/qda_fastrpc.h      | 15 ++++++++
 drivers/accel/qda/qda_fastrpc_shim.c | 30 ++++++++++++++++
 drivers/accel/qda/qda_ioctl.c        | 45 ++++++++++++++++++++++++
 drivers/accel/qda/qda_ioctl.h        |  2 ++
 drivers/accel/qda/qda_legacy_ops.c   |  1 +
 drivers/accel/qda/qda_legacy_ops.h   |  2 ++
 drivers/accel/qda/qda_rpmsg.c        |  3 ++
 drivers/accel/qda/qda_soc.c          | 33 ++++++++++++++++++
 drivers/accel/qda/qda_soc.h          |  3 ++
 include/uapi/drm/qda_accel.h         | 27 +++++++++++++++
 13 files changed, 233 insertions(+), 4 deletions(-)

diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 51d8ef10c6c1..912dcb2b593d 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -128,6 +128,7 @@ static const struct drm_ioctl_desc qda_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MMAP, qda_ioctl_mmap, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_MUNMAP, qda_ioctl_munmap, 0),
 	DRM_IOCTL_DEF_DRV(QDA_REMOTE_SESSION_ATTACH, qda_ioctl_attach, 0),
+	DRM_IOCTL_DEF_DRV(QDA_SET_OPTION, qda_ioctl_set_option, 0),
 };
 
 static const struct drm_driver qda_drm_driver = {
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index cbb580d03e0b..ff58bac77319 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -71,6 +71,12 @@ struct qda_session {
 	 * ioctls on one file can run concurrently.
 	 */
 	struct qda_gem_obj *init_mem_gem_obj;
+	/**
+	 * @poll_mode: Poll for completion of invocations on dynamically loaded
+	 * modules in a user PD. Set through the set-option ioctl, which can run
+	 * concurrently with invocations on the same file.
+	 */
+	bool poll_mode;
 	/**
 	 * @msg_buf_pool: Invoke header buffers this session is done with,
 	 * kept for its later invocations to reuse.
@@ -146,6 +152,8 @@ struct qda_dev {
 	struct mutex remote_heap_lock;
 	/** @audio_init_mem: True once the DSP has been given the remote heap */
 	bool audio_init_mem;
+	/** @poll_mode_supported: Sessions may opt in to invocation completion polling */
+	bool poll_mode_supported;
 };
 
 /**
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 6044cfad53e9..0617bb4842b5 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -3,6 +3,7 @@
 #include <linux/bitfield.h>
 #include <linux/capability.h>
 #include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
 #include <linux/limits.h>
 #include <linux/mm.h>
 #include <linux/sched.h>
@@ -89,8 +90,8 @@ static u64 calculate_page_aligned_size(u64 addr, u64 len)
  * import time unless they are. So the metadata region needs exactly one
  * qda_remote_buf, one qda_invoke_buf and one qda_phy_page per scalar; unlike
  * drivers/misc/fastrpc.c, which supports multi-page buffers and so may need
- * several qda_phy_page entries per scalar. It ends with the handle list and
- * the CRC list.
+ * several qda_phy_page entries per scalar. It ends with the handle list, the
+ * CRC list and the poll word.
  */
 static size_t qda_fastrpc_get_meta_size(struct qda_fastrpc_invoke_ctx *ctx)
 {
@@ -98,7 +99,8 @@ static size_t qda_fastrpc_get_meta_size(struct qda_fastrpc_invoke_ctx *ctx)
 		sizeof(struct qda_invoke_buf) +
 		sizeof(struct qda_phy_page)) * ctx->nscalars +
 		sizeof(u64) * QDA_FASTRPC_MAX_HANDLELIST +
-		sizeof(u32) * QDA_FASTRPC_MAX_CRCLIST;
+		sizeof(u32) * QDA_FASTRPC_MAX_CRCLIST +
+		sizeof(u32);
 }
 
 /**
@@ -518,6 +520,10 @@ static int qda_fastrpc_get_args(struct qda_fastrpc_invoke_ctx *ctx, struct qda_m
 	pages = (struct qda_phy_page *)&list[ctx->nscalars];
 	/* Kept so the handle list, which follows the pages, can be found later */
 	ctx->pages = pages;
+	/* The poll word follows the handle list and the CRC list */
+	ctx->poll_addr = (u32 *)((u8 *)(pages + ctx->nscalars) +
+				 sizeof(u64) * QDA_FASTRPC_MAX_HANDLELIST +
+				 sizeof(u32) * QDA_FASTRPC_MAX_CRCLIST);
 
 	for (i = 0; i < ctx->nbufs; ++i) {
 		rpra[i].buf.addr = 0;
@@ -598,6 +604,54 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
 	return 0;
 }
 
+static u32 qda_fastrpc_read_poll_addr(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	dma_rmb();
+
+	return READ_ONCE(*ctx->poll_addr);
+}
+
+/*
+ * Busy-wait for the DSP to write QDA_FASTRPC_POLL_RESPONSE into the poll word,
+ * which saves the interrupt and scheduling latency of waiting for the rpmsg
+ * response. The response still arrives afterwards and drops the reference
+ * qda_rpmsg_send_msg() took for it as usual.
+ *
+ * Return: 0 if the invocation completed, negative error code if it has not
+ * answered within QDA_FASTRPC_POLL_MAX_TIMEOUT_US
+ */
+static int qda_fastrpc_poll_for_rsp(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	u32 val;
+	int err;
+
+	err = read_poll_timeout_atomic(qda_fastrpc_read_poll_addr, val,
+				       val == QDA_FASTRPC_POLL_RESPONSE ||
+				       READ_ONCE(ctx->is_work_done),
+				       1, QDA_FASTRPC_POLL_MAX_TIMEOUT_US, false, ctx);
+	if (err)
+		return err;
+
+	/* The rpmsg path records its own status, so only the poll path's is set here */
+	if (val == QDA_FASTRPC_POLL_RESPONSE) {
+		WRITE_ONCE(ctx->is_work_done, true);
+		ctx->retval = 0;
+	}
+
+	return 0;
+}
+
+static int qda_fastrpc_wait_for_completion(struct qda_fastrpc_invoke_ctx *ctx)
+{
+	if (ctx->is_polled && !qda_fastrpc_poll_for_rsp(ctx))
+		return 0;
+
+	/* Polling timed out, so fall back to waiting for the rpmsg response */
+	ctx->is_polled = false;
+
+	return qda_rpmsg_wait_for_rsp(ctx);
+}
+
 /**
  * qda_fastrpc_flush_pending() - Fail every invocation still in flight
  * @qdev: Device whose invocations should be failed
@@ -617,6 +671,7 @@ void qda_fastrpc_flush_pending(struct qda_dev *qdev)
 
 	xa_for_each(&qdev->ctx_xa, idx, ctx) {
 		ctx->retval = -EPIPE;
+		WRITE_ONCE(ctx->is_work_done, true);
 		complete(&ctx->work);
 		kref_put(&ctx->refcount, qda_fastrpc_context_free);
 	}
@@ -680,11 +735,15 @@ int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ct
 	if (err)
 		return err;
 
+	/* Only invocations of a dynamically loaded module in a user PD are polled */
+	ctx->is_polled = READ_ONCE(ctx->session->poll_mode) && ctx->pd == QDA_USER_PD &&
+			 ctx->handle > QDA_FASTRPC_MAX_STATIC_HANDLE;
+
 	err = qda_rpmsg_send_msg(qdev, &msg);
 	if (err)
 		return err;
 
-	err = qda_rpmsg_wait_for_rsp(ctx);
+	err = qda_fastrpc_wait_for_completion(ctx);
 	if (err)
 		return err;
 
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
index 7d5cd04943a3..2e01d126addc 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -75,6 +75,15 @@ struct qda_session;
 #define QDA_USER_PD			1
 #define QDA_SENSORS_PD			2
 
+/* Handles up to this value name static DSP services, not loaded modules */
+#define QDA_FASTRPC_MAX_STATIC_HANDLE	20
+
+/* Value the DSP writes to the poll word once a polled invocation completes */
+#define QDA_FASTRPC_POLL_RESPONSE	0xdecaf
+
+/* How long to poll before falling back to waiting for the rpmsg response */
+#define QDA_FASTRPC_POLL_MAX_TIMEOUT_US	10000
+
 /* Largest ELF image a process may be created from */
 #define QDA_FASTRPC_INIT_FILELEN_MAX	(4 * 1024 * 1024)
 
@@ -342,6 +351,12 @@ struct qda_fastrpc_invoke_ctx {
 	struct drm_gem_object *map_obj;
 	/** @pages: Array of physical page descriptors inside the payload */
 	struct qda_phy_page *pages;
+	/** @poll_addr: Poll word at the end of the payload, written by the DSP */
+	u32 *poll_addr;
+	/** @is_polled: Wait for completion by polling @poll_addr first */
+	bool is_polled;
+	/** @is_work_done: Set once either the poll or the rpmsg path saw completion */
+	bool is_work_done;
 	/** @work: Completed once the DSP has answered */
 	struct completion work;
 	/**
diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
index 2be1dd116ef7..acd1b1b2f2b7 100644
--- a/drivers/accel/qda/qda_fastrpc_shim.c
+++ b/drivers/accel/qda/qda_fastrpc_shim.c
@@ -463,6 +463,34 @@ static long qda_shim_mem_unmap(struct qda_shim_file *sf, void __user *argp)
 	return 0;
 }
 
+/*
+ * The legacy request IDs and values match the accel ones, but they are
+ * translated rather than passed through, so the two ABIs stay independent.
+ */
+static long qda_shim_set_option(struct qda_shim_file *sf, void __user *argp)
+{
+	struct fastrpc_ioctl_set_option args;
+	int i;
+
+	if (copy_from_user(&args, argp, sizeof(args)))
+		return -EFAULT;
+
+	for (i = 0; i < ARRAY_SIZE(args.reserved); i++)
+		if (args.reserved[i])
+			return -EINVAL;
+
+	if (args.request_id != FASTRPC_POLL_MODE)
+		return -EINVAL;
+
+	if (args.value != FASTRPC_POLL_MODE_ENABLE &&
+	    args.value != FASTRPC_POLL_MODE_DISABLE)
+		return -EINVAL;
+
+	return qda_legacy_ops_table.set_option(sf->session, QDA_SET_OPTION_POLL_MODE,
+					       args.value == FASTRPC_POLL_MODE_ENABLE ?
+					       QDA_POLL_MODE_ENABLE : QDA_POLL_MODE_DISABLE);
+}
+
 static long qda_shim_get_dsp_info(struct qda_shim_file *sf, void __user *argp)
 {
 	struct fastrpc_ioctl_capability args;
@@ -570,6 +598,8 @@ static long qda_shim_dispatch(struct qda_shim_file *sf, unsigned int cmd, void _
 		return qda_shim_invoke(sf, argp);
 	case FASTRPC_IOCTL_GET_DSP_INFO:
 		return qda_shim_get_dsp_info(sf, argp);
+	case FASTRPC_IOCTL_SET_OPTION:
+		return qda_shim_set_option(sf, argp);
 	default:
 		/*
 		 * Includes FASTRPC_IOCTL_FREE_DMA_BUFF, which the driver this
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index c784defd0f70..f43fae60059d 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -413,3 +413,48 @@ int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_p
 {
 	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INVOKE_DYNAMIC, data, file_priv->driver_priv);
 }
+
+/**
+ * qda_session_set_option() - Set a per-session option
+ * @session: Session to update
+ * @request_id: Option to set (QDA_SET_OPTION_*)
+ * @value: Request-specific value
+ *
+ * Shared by the accel UAPI and the legacy shim's FASTRPC_IOCTL_SET_OPTION.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_session_set_option(struct qda_session *session, u32 request_id, u32 value)
+{
+	switch (request_id) {
+	case QDA_SET_OPTION_POLL_MODE:
+		if (!session->qda_dev->poll_mode_supported)
+			return -EOPNOTSUPP;
+
+		if (value == QDA_POLL_MODE_ENABLE)
+			WRITE_ONCE(session->poll_mode, true);
+		else if (value == QDA_POLL_MODE_DISABLE)
+			WRITE_ONCE(session->poll_mode, false);
+		else
+			return -EINVAL;
+
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * qda_ioctl_set_option() - Set a per-session option
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_set_option)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_set_option(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+	struct drm_qda_set_option *args = data;
+
+	return qda_session_set_option(file_priv->driver_priv, args->request_id, args->value);
+}
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 329f3809c8ce..277c16742196 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -20,6 +20,8 @@ int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *f
 int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv);
 int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_ioctl_set_option(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_session_set_option(struct qda_session *session, u32 request_id, u32 value);
 int qda_session_init_create(struct qda_session *session, struct qda_init_create_args *create);
 int qda_session_attach(struct qda_session *session, u32 pd_type, const char __user *name,
 		       u32 namelen);
diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
index 8d9d597c76c2..18c17d5a9d2b 100644
--- a/drivers/accel/qda/qda_legacy_ops.c
+++ b/drivers/accel/qda/qda_legacy_ops.c
@@ -198,6 +198,7 @@ const struct qda_legacy_ops qda_legacy_ops_table = {
 	.session_destroy = legacy_session_destroy,
 	.init_create = legacy_init_create,
 	.attach = qda_session_attach,
+	.set_option = qda_session_set_option,
 	.release = legacy_release,
 	.mmap = legacy_mmap,
 	.mem_map = legacy_mem_map,
diff --git a/drivers/accel/qda/qda_legacy_ops.h b/drivers/accel/qda/qda_legacy_ops.h
index c79003021fc1..ffb3ebd9b1c5 100644
--- a/drivers/accel/qda/qda_legacy_ops.h
+++ b/drivers/accel/qda/qda_legacy_ops.h
@@ -36,6 +36,8 @@ struct qda_legacy_ops {
 	/** @attach: Attach the session to a DSP-resident protection domain */
 	int (*attach)(struct qda_session *session, u32 pd_type, const char __user *name,
 		      u32 namelen);
+	/** @set_option: Set a per-session option */
+	int (*set_option)(struct qda_session *session, u32 request_id, u32 value);
 	/** @release: Release the session's DSP process, if it has one */
 	int (*release)(struct qda_session *session);
 	/** @mmap: Map a buffer into the DSP's address space */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index b93e906b5793..c2a6798cb50d 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -132,6 +132,7 @@ static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
 	}
 
 	ctx->retval = resp_msg->retval;
+	WRITE_ONCE(ctx->is_work_done, true);
 	complete(&ctx->work);
 
 	/*
@@ -267,6 +268,8 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
 	if (!qdev->soc_data)
 		return -ENODEV;
 
+	qdev->poll_mode_supported = qda_soc_poll_mode_supported(qdev->soc_data);
+
 	ret = of_property_read_string(rpdev->dev.of_node, "label", &qdev->dsp_name);
 	if (ret) {
 		dev_err(&rpdev->dev, "Missing 'label' property in DT node: %d\n", ret);
diff --git a/drivers/accel/qda/qda_soc.c b/drivers/accel/qda/qda_soc.c
index fb316b74103d..c670f747e1f5 100644
--- a/drivers/accel/qda/qda_soc.c
+++ b/drivers/accel/qda/qda_soc.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 // Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/of.h>
 #include <linux/string.h>
 #include "qda_drv.h"
 #include "qda_soc.h"
@@ -8,6 +9,7 @@ const struct qda_soc_data kaanapali_soc_data = {
 	.sid_pos = 56,
 	.dma_addr_bits_cdsp = 34,
 	.dma_addr_bits_default = 32,
+	.poll_mode_supported = true,
 };
 
 const struct qda_soc_data default_soc_data = {
@@ -16,6 +18,37 @@ const struct qda_soc_data default_soc_data = {
 	.dma_addr_bits_default = 32,
 };
 
+/*
+ * Platforms that share default_soc_data but whose DSP firmware does implement
+ * invocation completion polling. The list is closed: new SoCs advertise
+ * polling support through their own qda_soc_data instead.
+ */
+static const struct of_device_id qda_poll_mode_machines[] = {
+	{ .compatible = "qcom,milos" },
+	{ .compatible = "qcom,qcs8300" },
+	{ .compatible = "qcom,sa8775p" },
+	{ .compatible = "qcom,sar2130p" },
+	{ .compatible = "qcom,sm8450" },
+	{ .compatible = "qcom,sm8550" },
+	{ .compatible = "qcom,sm8650" },
+	{ .compatible = "qcom,sm8750" },
+	{ .compatible = "qcom,x1e80100" },
+	{ .compatible = "qcom,x1p42100" },
+	{ }
+};
+
+/**
+ * qda_soc_poll_mode_supported() - Whether the DSP supports completion polling
+ * @soc_data: SoC parameters of the device
+ *
+ * Return: true if invocations on this SoC may use poll mode
+ */
+bool qda_soc_poll_mode_supported(const struct qda_soc_data *soc_data)
+{
+	return soc_data->poll_mode_supported ||
+	       of_machine_get_match(qda_poll_mode_machines);
+}
+
 /**
  * qda_soc_dma_addr_bits() - DMA address width of a device's context banks
  * @qdev: QDA device
diff --git a/drivers/accel/qda/qda_soc.h b/drivers/accel/qda/qda_soc.h
index fb5b786d0928..db8f10a30f7b 100644
--- a/drivers/accel/qda/qda_soc.h
+++ b/drivers/accel/qda/qda_soc.h
@@ -24,11 +24,14 @@ struct qda_soc_data {
 	u32 dma_addr_bits_cdsp;
 	/** @dma_addr_bits_default: DMA address width of all other context banks */
 	u32 dma_addr_bits_default;
+	/** @poll_mode_supported: DSP firmware supports invocation completion polling */
+	bool poll_mode_supported;
 };
 
 extern const struct qda_soc_data kaanapali_soc_data;
 extern const struct qda_soc_data default_soc_data;
 
+bool qda_soc_poll_mode_supported(const struct qda_soc_data *soc_data);
 u32 qda_soc_dma_addr_bits(const struct qda_dev *qdev);
 
 /**
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 07b943a3c822..816f2c10accf 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -20,6 +20,7 @@ extern "C" {
 #define DRM_QDA_REMOTE_MMAP		0x05
 #define DRM_QDA_REMOTE_MUNMAP		0x06
 #define DRM_QDA_REMOTE_SESSION_ATTACH	0x07
+#define DRM_QDA_SET_OPTION		0x08
 
 #define DRM_IOCTL_QDA_QUERY		DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_QUERY, \
 					 struct drm_qda_query)
@@ -39,6 +40,8 @@ extern "C" {
 #define DRM_IOCTL_QDA_REMOTE_SESSION_ATTACH					\
 	DRM_IOW(DRM_COMMAND_BASE + DRM_QDA_REMOTE_SESSION_ATTACH,		\
 		struct drm_qda_remote_attach)
+#define DRM_IOCTL_QDA_SET_OPTION	DRM_IOW(DRM_COMMAND_BASE + DRM_QDA_SET_OPTION, \
+					 struct drm_qda_set_option)
 
 /* Request types for drm_qda_mem_map.request */
 #define QDA_REQUEST_MMAP	1	/* Map a whole buffer */
@@ -248,6 +251,30 @@ struct drm_qda_remote_attach {
 	__u64 name;
 };
 
+/* Request IDs for drm_qda_set_option.request_id */
+#define QDA_SET_OPTION_POLL_MODE	1
+
+/* Values for drm_qda_set_option.value with QDA_SET_OPTION_POLL_MODE */
+#define QDA_POLL_MODE_DISABLE	0
+#define QDA_POLL_MODE_ENABLE	1
+
+/**
+ * struct drm_qda_set_option - Set a per-session option
+ * @request_id: Option to set (QDA_SET_OPTION_*)
+ * @value: Request-specific value
+ *
+ * For QDA_SET_OPTION_POLL_MODE, @value is QDA_POLL_MODE_ENABLE or
+ * QDA_POLL_MODE_DISABLE. Enabling poll mode lets the driver poll shared
+ * memory for completion of a dynamically loaded module's invocations in a
+ * user PD instead of waiting for an interrupt-based response, trading CPU
+ * cycles for lower round-trip latency. DSPs that do not support it reject
+ * the request with EOPNOTSUPP.
+ */
+struct drm_qda_set_option {
+	__u32 request_id;
+	__u32 value;
+};
+
 #if defined(__cplusplus)
 }
 #endif

-- 
2.34.1


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

* Re: [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver
  2026-11-01 22:21 ` [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
@ 2026-11-01 22:39   ` sashiko-bot
  2026-11-02  8:42   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:39 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] accel/qda: !DRM_ACCEL_QDA in Kconfig allows building both drivers as modules
--

commit 546c9c847d7dbca90dfe6d1ba0861765d71753de
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add initial QDA DRM accelerator driver

This commit adds the initial DRM compute accelerator skeleton driver for
Qualcomm Hexagon DSPs (qda), binding to the same RPMsg device tree
compatibles as the legacy fastrpc driver. It also adds a Kconfig dependency
on QCOM_FASTRPC intended to make the two drivers mutually exclusive.

> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index c40e89cc57243..14b7bbaadcaa0 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -269,6 +269,7 @@ config QCOM_FASTRPC
>  	tristate "Qualcomm FastRPC"
>  	depends on ARCH_QCOM || COMPILE_TEST
>  	depends on RPMSG
> +	depends on !DRM_ACCEL_QDA

[Severity: Medium]
As reported by Sashiko on v1 [1], since DRM_ACCEL_QDA in
drivers/accel/qda/Kconfig and QCOM_FASTRPC in drivers/misc/Kconfig are both
tristate symbols, does "depends on !DRM_ACCEL_QDA" still allow both drivers
to be built together as modules?

In Kconfig tristate logic (EXPR_NOT in scripts/kconfig/expr.h), when
DRM_ACCEL_QDA=m, !DRM_ACCEL_QDA evaluates to m rather than n. That only
prevents QCOM_FASTRPC=y when DRM_ACCEL_QDA=m, while still allowing
QCOM_FASTRPC=m and DRM_ACCEL_QDA=m at the same time. Both modules then
export the same OF device table entries in qda_rpmsg_of_match and race to
bind to the same RPMsg devices.

Should this dependency instead be written as:

	depends on DRM_ACCEL_QDA=n

so that QCOM_FASTRPC is disabled whenever DRM_ACCEL_QDA is enabled as either
built-in or a module?

[1] https://lore.kernel.org/r/20260519-qda-series-v1-3-b2d984c297f8@oss.qualcomm.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=2

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

* Re: [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch
  2026-11-01 22:21 ` [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch Ekansh Gupta
@ 2026-11-01 22:39   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:39 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] accel/qda: use-after-free in qda_rpmsg_cb() due to unlocked xa_load()
- [High] accel/qda: duplicate response refcount underflow in qda_rpmsg_cb()
- [High] accel/qda: unflushed put_work and in-flight contexts in qda_rpmsg_remove()
- [High] accel/qda: missing PD exit sentinel filtering in qda_rpmsg_cb()
--

commit 66df96a5b81c60ce1864c2d544af7d04014ec8e1
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add RPMsg send and response dispatch

This commit adds RPMsg message transmission and response callback handling
for FastRPC invocations, along with the wire message definitions and workqueue
reference cleanup.

> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 14034e250555d..43b56ab3f0128 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
[ ... ]
> +static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
> +			void *priv, u32 src)
> +{
> +	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, FIELD_GET(QDA_FASTRPC_CTXID_MASK, resp_msg->ctx));
> +	if (!ctx) {
> +		drm_dbg_driver(&qdev->drm_dev, "No context matches response 0x%llx\n",
> +			       resp_msg->ctx);
> +		ret = -ENOENT;
> +		goto out_exit;
> +	}

[Severity: High]
When a protection domain exits on the DSP, the firmware sends a PD exit
notification carrying the sentinel value 0xABCDABCD in resp_msg->ctx (which
drivers/misc/fastrpc.c filters out via FASTRPC_DSP_PD_NOTIFY_CTX in
fastrpc_rpmsg_callback()).

Under the QDA context ID layout, FIELD_GET(QDA_FASTRPC_CTXID_MASK, 0xABCDABCD)
evaluates to 0xCD (205) and FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, 0xABCDABCD)
evaluates to 0xABCD. If a PD exit notification arrives while context slot 0xCD
is occupied by an invocation at sequence number 0xABCD, won't qda_rpmsg_cb()
mistakenly match it, overwrite ctx->retval, wake the caller early via
complete(&ctx->work) while the DSP is still accessing its DMA buffers, and
schedule ctx->put_work?

Additionally, when slot 0xCD is empty, returning -ENOENT here instead of 0
causes SMD-backed RPMsg channels (qcom_smd_channel_recv_single() in
drivers/rpmsg/qcom_smd.c) to stop advancing the RX FIFO tail. Should
qda_rpmsg_cb() filter out 0xABCDABCD early and return 0 when dropping
unmatched responses?

> +
> +	/*
> +	 * Context IDs are only 8 bits wide and are recycled once a context is
> +	 * freed, so a late or duplicate response may name an ID that now belongs
> +	 * to a newer context. The sequence number tells the two apart.
> +	 */
> +	if (FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, resp_msg->ctx) !=
> +	    FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, ctx->ctxid)) {
> +		drm_dbg_driver(&qdev->drm_dev, "Dropping stale response 0x%llx\n",
> +			       resp_msg->ctx);
> +		goto out_exit;
> +	}

[Severity: High]
Can this unlocked xa_load() race with context teardown and cause a
use-after-free in qda_rpmsg_cb()?

When the final reference on ctx drops to zero on another CPU (for example,
when qda_rpmsg_send_msg() fails and drops the callback reference followed by
the caller dropping its reference, or when an invocation finishes),
qda_fastrpc_context_free() removes ctx from qdev->ctx_xa and immediately frees
it without an RCU grace period:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_free() {
	struct qda_fastrpc_invoke_ctx *ctx =
		container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);

	xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid));
	kfree(ctx);
}

Because xa_load(&qdev->ctx_xa, ...) only holds rcu_read_lock() internally
during the lookup and returns the raw ctx pointer without holding
qdev->ctx_xa.xa_lock or pinning ctx->refcount via kref_get_unless_zero(), if
a late or duplicate response arrives in qda_rpmsg_cb() just before xa_erase()
and kfree(ctx), qda_rpmsg_cb() will dereference freed memory at ctx->ctxid,
write ctx->retval, and call complete(&ctx->work) and
schedule_work(&ctx->put_work) on the freed object.

Similarly, in qda_fastrpc_context_alloc(), xa_alloc() publishes ctx into
qdev->ctx_xa before ctx->ctxid is initialized:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_alloc() {
	...
	ret = xa_alloc(&qdev->ctx_xa, &id, ctx, XA_LIMIT(1, QDA_FASTRPC_CTXID_MAX),
		       GFP_KERNEL);
	if (ret) {
		kfree(ctx);
		return ERR_PTR(ret);
	}

	ctx->ctxid = FIELD_PREP(QDA_FASTRPC_CTXID_MASK, id) |
		     FIELD_PREP(QDA_FASTRPC_CTXID_SEQ_MASK,
				(u64)atomic64_inc_return(&qdev->ctx_seq));
}

which briefly exposes ctx with ctx->ctxid == 0 to concurrent lookups in
qda_rpmsg_cb().

> +
> +	ctx->retval = resp_msg->retval;
> +	complete(&ctx->work);
> +
> +	/*
> +	 * Release the reference taken by qda_rpmsg_send_msg(). This callback
> +	 * runs in atomic context, and if it drops the last reference the
> +	 * context's DMA buffers are freed, which must not happen here.
> +	 */
> +	schedule_work(&ctx->put_work);

[Severity: High]
What prevents a duplicate or unsolicited DSP response from scheduling
ctx->put_work without a corresponding callback reference and underflowing
ctx->refcount?

In qda_fastrpc_context_alloc(), ctx is inserted into qdev->ctx_xa with
refcount = 1 (owned by the caller), and qda_rpmsg_send_msg() takes a single
additional reference via kref_get(&ctx->refcount) for the response handler.
When a valid response arrives in qda_rpmsg_cb(), ctx is neither removed from
qdev->ctx_xa nor atomically marked as already answered; it stays in
qdev->ctx_xa until qda_fastrpc_context_free() runs at refcount == 0.

Once the first put_work begins executing in qda_fastrpc_context_put_wq() and
drops refcount from 2 to 1 while the caller still holds its reference, a
duplicate response from the DSP carrying the same ctx value (matching both the
8-bit ID and the sequence number) will match ctx in qda_rpmsg_cb() a second
time and call schedule_work(&ctx->put_work) again. That second execution of
qda_fastrpc_context_put_wq() drops the caller's reference, freeing ctx while
the caller is still using it and triggering a refcount underflow when the
caller drops its own reference.

Likewise, because ctx is in qdev->ctx_xa before qda_rpmsg_send_msg() is called
(and remains in qdev->ctx_xa after rpmsg_send() fails and drops the callback
reference), an unsolicited response matching ctx->ctxid before send or after a
failed send will also schedule ctx->put_work without a matching callback
reference.

> +
> +out_exit:
> +	drm_dev_exit(idx);
> +
> +	return ret;
> +}
> +
>  static void qda_rpmsg_remove(struct rpmsg_device *rpdev)

[Severity: High]
If the RPMsg device is removed while ctx->put_work is queued or while
invocations are in flight, can qda_fastrpc_context_put_wq() access
qdev->ctx_xa after qda_deinit_device() has destroyed it?

In qda_rpmsg_cb(), schedule_work(&ctx->put_work) queues
qda_fastrpc_context_put_wq() on system_percpu_wq and immediately exits the
drm_dev_enter() critical section via drm_dev_exit(idx). When
qda_rpmsg_remove() runs:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_remove() {
	...
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_cb_unpopulate(qdev);
	qda_deinit_device(qdev);
}

drm_dev_unplug() only waits for active drm_dev_enter() sections; it does not
flush or wait for pending ctx->put_work items before qda_deinit_device(qdev)
destroys qdev->ctx_xa via xa_destroy(&qdev->ctx_xa) (and if all DRM files are
closed, devm frees qdev when qda_rpmsg_remove() returns). When the queued
qda_fastrpc_context_put_wq() worker subsequently runs,
qda_fastrpc_context_free() calls xa_erase(&ctx->qdev->ctx_xa, ...) on the
destroyed XArray and freed qdev.

In addition, if the RPMsg device is removed in this commit while invocations
sent via qda_rpmsg_send_msg() are waiting for a response, qda_rpmsg_remove()
does not complete ctx->work to wake waiters in qda_rpmsg_wait_for_rsp() or
drop the callback references on entries remaining in qdev->ctx_xa before
xa_destroy(). Although a later commit in the series, commit 9e89a77ec2ad
("accel/qda: Add the invocation ioctl and teardown on unplug"), adds
qda_fastrpc_flush_pending(qdev) to qda_rpmsg_remove() to wake and put
remaining entries in ctx_xa, it still does not flush pending ctx->put_work
items that were already scheduled by qda_rpmsg_cb().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=11

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

* Re: [PATCH v4 04/25] accel/qda: Assign a context bank to each session
  2026-11-01 22:21 ` [PATCH v4 04/25] accel/qda: Assign a context bank to each session Ekansh Gupta
@ 2026-11-01 22:39   ` sashiko-bot
  2026-11-02  8:45   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:39 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] accel/qda: dangling cb_dev pointer in struct qda_iommu_device after unpopulate
- [High] accel/qda: devm lifetime mismatch and premature teardown of qdev->iommu_mgr
--

commit 274f69c69f358c698c8f7dd37051492c7c060cc1
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Assign a context bank to each session

This commit introduces a context bank registry (struct qda_memory_manager) to
track a QDA device's compute context banks and exclusively assign one refcounted
struct qda_iommu_device per open session.

> diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
> index afb0bb71a1f44..6f1cff1133f9d 100644
> --- a/drivers/accel/qda/qda_cb.c
> +++ b/drivers/accel/qda/qda_cb.c
> @@ -7,8 +7,44 @@
>  #include <drm/drm_print.h>
>  #include "qda_cb.h"
>  #include "qda_drv.h"
> +#include "qda_memory_manager.h"
>  #include "qda_soc.h"
>  
> +static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev)
> +{
> +	struct qda_iommu_device *iommu_dev;
> +	int ret;
> +
> +	iommu_dev = kzalloc_obj(*iommu_dev);
> +	if (!iommu_dev)
> +		return -ENOMEM;
> +
> +	iommu_dev->dev = cb_dev;
> +	iommu_dev->qdev = qdev;
> +
> +	ret = qda_memory_manager_register_device(qdev->iommu_mgr, iommu_dev);
> +	if (ret) {
> +		drm_err(&qdev->drm_dev, "Failed to register context bank: %d\n", ret);
> +		kfree(iommu_dev);
> +		return ret;
> +	}
> +
> +	dev_set_drvdata(cb_dev, iommu_dev);
> +
> +	return 0;
> +}
> +
> +static void qda_cb_teardown_device(struct device *cb_dev)
> +{
> +	struct qda_iommu_device *iommu_dev = dev_get_drvdata(cb_dev);
> +
> +	if (!iommu_dev)
> +		return;
> +
> +	qda_memory_manager_unregister_device(iommu_dev->qdev->iommu_mgr, iommu_dev);
> +	dev_set_drvdata(cb_dev, NULL);
> +}

[ ... ]

> @@ -70,6 +112,7 @@ void qda_cb_unpopulate(struct qda_dev *qdev)
>  
>  	list_for_each_entry_safe(entry, tmp, &qdev->cb_devs, node) {
>  		list_del(&entry->node);
> +		qda_cb_teardown_device(&entry->pdev->dev);
>  		of_platform_device_destroy(&entry->pdev->dev, NULL);

[Severity: High]
Can this leave a dangling pointer in iommu_dev->dev when the rpmsg channel
goes away while a session is still open?

In qda_cb_setup_device(), iommu_dev->dev stores the raw cb_dev pointer
(&pdev->dev) without taking a reference via get_device(cb_dev), and
iommu_device_free() frees struct qda_iommu_device without calling
put_device().

When a session owns a context bank via qda_memory_manager_assign_device(),
assign_available_device() takes a second reference on iommu_dev->ref so that
struct qda_iommu_device stays alive until qda_postclose() ->
qda_session_free() calls qda_memory_manager_release_device().

If qda_rpmsg_remove() runs while a DRM file is open:

  qda_rpmsg_remove()
    qda_cb_unpopulate()
      qda_cb_teardown_device(&entry->pdev->dev)
        qda_memory_manager_unregister_device()
          kref_put(&iommu_dev->ref, iommu_device_free)  /* ref drops 2 -> 1 */
      of_platform_device_destroy(&entry->pdev->dev, NULL)

of_platform_device_destroy() unregisters and frees entry->pdev (and its
embedded struct device), while session->assigned_iommu_dev remains alive with
iommu_dev->dev still pointing to the freed struct device for the remainder of
the open session's lifetime.

Should qda_cb_setup_device() and iommu_device_free() manage a device
reference with get_device() and put_device(), or otherwise invalidate
iommu_dev->dev under synchronization when the context bank is torn down?

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 6ecb6a966f686..a99e23933f6ba 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c

[ ... ]

> @@ -70,6 +76,26 @@ struct qda_dev *qda_alloc_device(struct device *dev)
>  	return qdev;
>  }
>  
> +int qda_init_device(struct qda_dev *qdev, int num_cbs)
> +{
> +	int ret;
> +
> +	qdev->iommu_mgr = devm_kzalloc(qdev->dev, sizeof(*qdev->iommu_mgr), GFP_KERNEL);
> +	if (!qdev->iommu_mgr)
> +		return -ENOMEM;
> +
> +	ret = qda_memory_manager_init(qdev->iommu_mgr, num_cbs);
> +	if (ret)
> +		drm_err(&qdev->drm_dev, "Failed to initialize memory manager: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +void qda_deinit_device(struct qda_dev *qdev)
> +{
> +	qda_memory_manager_exit(qdev->iommu_mgr);
> +}

[Severity: High]
Does allocating qdev->iommu_mgr with devm_kzalloc(qdev->dev, ...) and tearing
it down in qda_rpmsg_remove() -> qda_deinit_device() mismatch the lifetime of
struct qda_dev?

qda_alloc_device() allocates struct qda_dev with devm_drm_dev_alloc(), which
keeps qdev alive until the last drm_device reference is dropped when all open
drm_file descriptors are closed. In contrast, devm_kzalloc(qdev->dev, ...)
ties qdev->iommu_mgr to the rpmsg_device driver binding.

When qda_rpmsg_remove() runs while a file is open:

  qda_rpmsg_remove()
    drm_dev_unplug(&qdev->drm_dev)
    qda_cb_unpopulate(qdev)
    qda_deinit_device(qdev)
      qda_memory_manager_exit(qdev->iommu_mgr)
        kfree(mem_mgr->devices)
        mutex_destroy(&mem_mgr->assignment_lock)

When qda_rpmsg_remove() returns, devres releases qdev->iommu_mgr, leaving
qdev->iommu_mgr as a non-NULL dangling pointer to freed memory and a destroyed
mutex while qdev and open qda_session instances are still alive. Moreover,
because drm_ioctl() only performs a point-in-time drm_dev_is_unplugged() check
at entry before calling the ioctl handler, any in-flight operation that passed
that check before drm_dev_unplug() and calls
qda_memory_manager_assign_device(qdev->iommu_mgr, session) can race with
qda_memory_manager_exit() and devres teardown.

Should struct qda_memory_manager be embedded in struct qda_dev (or managed via
drmm) so its lifetime and mutex match qdev->drm_dev?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=4

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

* Re: [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug
  2026-11-01 22:21 ` [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug Ekansh Gupta
@ 2026-11-01 22:41   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:41 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] accel/qda: unconditional kref_put() in qda_fastrpc_flush_pending()
- [Critical] accel/qda: premature teardown in qda_rpmsg_remove() while ctxs active
- [Critical] accel/qda: missing init handle check in qda_fastrpc_prepare_args_invoke()
- [Critical] accel/qda: missing bounds check and union misuse in process_dma_handle()
- [Medium] accel/qda: handle list leak on DSP error in qda_fastrpc_invoke_common()
--

commit 9e89a77ec2ad7f5231cb9a9e8fb15165b01e374a
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add the invocation ioctl and teardown on unplug

This commit adds the DRM_IOCTL_QDA_REMOTE_INVOKE ioctl to allocate an
invocation context, prepare arguments, pack and send the payload over
RPMsg, and wait for the DSP response. It also adds
qda_fastrpc_flush_pending() to fail and wake pending invocations when
the RPMsg device is removed.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 2971f2f9461fc..3a1959e2c53ee 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
> @@ -10,6 +10,7 @@
>  #include "qda_drv.h"
>  #include "qda_fastrpc.h"
>  #include "qda_gem.h"
> +#include "qda_rpmsg.h"
>  
>  /*
>   * The caller owns the returned reference and must drop it with
> @@ -484,6 +485,72 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
>  	return 0;
>  }
>  
> +/**
> + * qda_fastrpc_flush_pending() - Fail every invocation still in flight
> + * @qdev: Device whose invocations should be failed
> + *
> + * The DSP has gone away and will never answer, so stand in for the response
> + * handler: wake each waiter with an error and drop the reference the send
> + * path took for that handler. Each waiter then tears its invocation down,
> + * releasing the buffers it holds.
> + *
> + * Must run after drm_dev_unplug() has drained the response handler, so this
> + * is the only path completing an invocation.
> + */
> +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);
> +	}
> +}

[Severity: Critical]
Can qda_fastrpc_flush_pending() drop a reference that was never taken (or was
already queued to be dropped), causing a reference count underflow and
use-after-free of struct qda_fastrpc_invoke_ctx?

In qda_fastrpc_invoke(), qda_fastrpc_context_alloc() initializes
ctx->refcount to 1 and immediately inserts ctx into qdev->ctx_xa before
qda_fastrpc_prepare_args(), qda_fastrpc_invoke_pack(), and
qda_rpmsg_send_msg() run:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_alloc() {
	...
	kref_init(&ctx->refcount);
	...
	ret = xa_alloc(&qdev->ctx_xa, &id, ctx, XA_LIMIT(1, QDA_FASTRPC_CTXID_MAX),
		       GFP_KERNEL);
}

Because drm_ioctl() only checks drm_dev_is_unplugged() at entry and does not
hold a drm_dev_enter() read lock across qda_ioctl_invoke(), drm_dev_unplug()
in qda_rpmsg_remove() does not wait for in-flight qda_fastrpc_invoke() calls.
The second reference for the response handler is only acquired inside
qda_rpmsg_send_msg() if drm_dev_enter() succeeds, qdev->rpdev is non-NULL,
and rpmsg_send() succeeds:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_send_msg() {
	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);
	}
}

Similarly, if qda_rpmsg_cb() handled a response just before unplug, it
schedules ctx->put_work to drop the response reference asynchronously while
ctx remains in qdev->ctx_xa until qda_fastrpc_context_free() runs:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_cb() {
	...
	ctx->retval = resp_msg->retval;
	complete(&ctx->work);
	schedule_work(&ctx->put_work);
}

When qda_fastrpc_flush_pending() iterates qdev->ctx_xa unlocked and
unconditionally calls kref_put(&ctx->refcount, qda_fastrpc_context_free), it
drops the caller's or worker's reference in both windows, freeing ctx while
qda_fastrpc_invoke() or qda_fastrpc_context_put_wq() is still using it. In
addition, because xa_for_each() runs without locking or pinning ctx, a
concurrent caller or put_work worker can free ctx right after xa_for_each()
loads the pointer, causing qda_fastrpc_flush_pending() to write ctx->retval
and call complete() and kref_put() on freed memory.

> +
> +/**
> + * qda_fastrpc_invoke_common() - Shared tail of a FastRPC invocation
> + * @qdev: Device to invoke on
> + * @ctx: Invocation whose arguments are already prepared
> + *
> + * Packs the invocation into a message, sends it, waits for the DSP, and acts
> + * on the answer. Common to the accel UAPI and the legacy shim, once each has
> + * prepared ctx->args and ctx->gem_objs by its own means.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ctx *ctx)
> +{
> +	struct qda_msg msg = {};
> +	int err;
> +
> +	err = qda_fastrpc_invoke_pack(ctx, &msg);
> +	if (err)
> +		return err;

[Severity: Critical]
When qda_fastrpc_invoke_common() calls qda_fastrpc_invoke_pack() ->
qda_fastrpc_get_args() -> process_dma_handle() for handle arguments
(i >= ctx->nbufs), can user space forge DSP DMA handle descriptors or trigger
out-of-bounds DSP memory accesses?

In process_dma_handle():

drivers/accel/qda/qda_fastrpc.c:process_dma_handle() {
	if (!ctx->args[i].handle) {
		rpra[i].buf.addr = ctx->args[i].offset;
		rpra[i].buf.len = ctx->args[i].length;
		return 0;
	}
	...
	/* 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].offset;
}

First, rpra[i] is a union qda_remote_arg, which the DSP interprets as struct
qda_remote_dmahandle dma (s32 handle; u32 offset; u32 len;) for handle
arguments (i >= ctx->nbufs). When ctx->args[i].handle is 0, writing the
64-bit ctx->args[i].offset into rpra[i].buf.addr aliases rpra[i].dma.handle
with the lower 32 bits and rpra[i].dma.offset with the upper 32 bits,
allowing user space to inject an arbitrary non-zero DSP handle and offset
while bypassing get_gem_obj_from_handle() validation.

Second, when ctx->args[i].handle is non-zero, process_dma_handle() looks up
gem_obj via get_gem_obj_from_handle(), but unlike process_msg_buffer(), it
never verifies that ctx->args[i].offset and ctx->args[i].length fit within
to_qda_gem_obj(gem_obj)->size (or within u32 before truncating them to
rpra[i].dma.offset and rpra[i].dma.len).

> +
> +	err = qda_rpmsg_send_msg(qdev, &msg);
> +	if (err)
> +		return err;
> +
> +	err = qda_rpmsg_wait_for_rsp(ctx);
> +	if (err)
> +		return err;
> +
> +	/* Ensure the results written by the DSP are visible before reading */
> +	dma_rmb();
> +
> +	err = ctx->retval;
> +	if (err) {
> +		drm_dbg_driver(&qdev->drm_dev, "DSP returned status %d\n", ctx->retval);
> +		return err;
> +	}
> +
> +	qda_fastrpc_cleanup_handlelist(ctx);
> +
> +	return 0;
> +}

[Severity: Medium]
Does returning early when ctx->retval is non-zero in
qda_fastrpc_invoke_common() leak the GEM handles listed in the DSP's response
handle list?

When a remote DSP invocation fails with a non-zero status code, the DSP
framework can still populate the handle release list in the payload buffer
(as addressed in drivers/misc/fastrpc.c by commit 1c8093591d1e ("misc:
fastrpc: Clean buffers on remote invocation failures")). Returning before
qda_fastrpc_cleanup_handlelist(ctx) leaves those GEM handles open in
ctx->session->file.

[ ... ]

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index b11743ab791e7..e9f4151e344af 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -6,6 +6,7 @@
>  #include <drm/drm_gem.h>
>  #include <drm/qda_accel.h>
>  #include "qda_drv.h"
> +#include "qda_fastrpc.h"
>  #include "qda_gem.h"
>  #include "qda_ioctl.h"
>  
> @@ -73,3 +74,38 @@ 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(void *data, struct qda_session *session)
> +{
> +	struct qda_dev *qdev = session->qda_dev;
> +	struct qda_fastrpc_invoke_ctx *ctx;
> +	int err;
> +
> +	ctx = qda_fastrpc_context_alloc(qdev);
> +	if (IS_ERR(ctx))
> +		return PTR_ERR(ctx);
> +
> +	err = qda_fastrpc_prepare_args(ctx, session, data);
> +	if (err)
> +		goto out_put;
> +
> +	err = qda_fastrpc_invoke_common(qdev, ctx);

[Severity: Critical]
Should qda_fastrpc_invoke() or qda_fastrpc_prepare_args_invoke() reject
user-space invocations targeting handle 1 (QDA_FASTRPC_INIT_HANDLE)?

In qda_fastrpc_prepare_args() -> qda_fastrpc_prepare_args_invoke():

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_prepare_args_invoke() {
	...
	ctx->handle = invoke_args->handle;
	ctx->sc = invoke_args->sc;
}

In the FastRPC protocol, handle 1 is reserved for kernel-issued process and
protection domain management calls. Unlike fastrpc_internal_invoke() in
drivers/misc/fastrpc.c, which rejects user-space calls with handle ==
FASTRPC_INIT_HANDLE with -EPERM (CVE-2019-2308), DRM_IOCTL_QDA_REMOTE_INVOKE
allows unprivileged user space to invoke handle 1 directly.

[ ... ]

> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 43b56ab3f0128..7e58df3eb2e17 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
> @@ -160,6 +160,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);
>  }

[Severity: Critical]
Does qda_rpmsg_remove() tear down qdev->cb_devs, qdev->iommu_mgr, and
qdev->ctx_xa while woken invocations and pending ctx->put_work items are
still using them?

When an invocation is waiting in qda_rpmsg_wait_for_rsp(), ctx->refcount is 2
(one held by the caller in qda_fastrpc_invoke() and one taken by
qda_rpmsg_send_msg()). Calling qda_fastrpc_flush_pending() wakes the waiter
and drops one reference, reducing ctx->refcount to 1, while ctx remains in
qdev->ctx_xa until the woken caller thread (or a pending ctx->put_work
worker) runs qda_fastrpc_context_free():

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_free() {
	...
	xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid));
	...
	if (ctx->msg_gem_obj)
		qda_session_msg_buf_put(ctx->session, &ctx->msg_gem_obj->base);

	if (ctx->session)
		qda_session_put(ctx->session);
	...
}

Because qda_rpmsg_remove() immediately proceeds to qda_cb_unpopulate(qdev)
and qda_deinit_device(qdev) without waiting for in-flight invocations or
flushing pending ctx->put_work items:

1. qda_cb_unpopulate() destroys and frees the child context-bank platform
devices via of_platform_device_destroy(&entry->pdev->dev, NULL) while GEM
objects held by active invocations and sessions still reference
&entry->pdev->dev via gem_obj->iommu_dev->dev, leading to a use-after-free in
qda_dma_free() when dma_free_coherent() runs.

2. qda_deinit_device() calls qda_memory_manager_exit(qdev->iommu_mgr) and
xa_destroy(&qdev->ctx_xa) while contexts are still present in qdev->ctx_xa;
when woken threads or ctx->put_work workers subsequently execute
qda_fastrpc_context_free(), they call xa_erase() on the destroyed XArray.

3. If an interrupted caller already returned and closed its DRM file while
ctx->put_work is still queued (since neither qda_session nor
qda_fastrpc_invoke_ctx holds a drm_dev_get() reference on qdev->drm_dev),
finishing qda_rpmsg_remove() allows devm to release qdev before
qda_fastrpc_context_put_wq() runs qda_fastrpc_context_free() and dereferences
ctx->qdev.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=13

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

* Re: [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton
  2026-11-01 22:21 ` [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton Ekansh Gupta
@ 2026-11-01 22:42   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:42 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] accel/qda: unplug race in qda_shim_ioctl() due to missing drm_dev_enter()
- [Critical] accel/qda: UAF and CB reuse when DMA-BUF outlives qda_shim_release()
- [High] accel/qda: GEM object leak on DMA-BUF mmap() due to NULL vm_ops
--

commit a13f2a6988b9b1d52b35c9e30bbc2ea3fc109ecb
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add legacy FastRPC shim skeleton

This commit adds a separate compatibility module that exposes /dev/fastrpc-*
misc device nodes per DSP domain on top of the QDA driver and implements the
FASTRPC_IOCTL_ALLOC_DMA_BUFF ioctl by exporting buffers as DMA-BUF file
descriptors.

> diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
> new file mode 100644
> index 0000000000000..25f5c242efde1
> --- /dev/null
> +++ b/drivers/accel/qda/qda_fastrpc_shim.c

[ ... ]

> +static int qda_shim_release(struct inode *inode, struct file *file)
> +{
> +	struct qda_shim_file *sf = file->private_data;
> +
> +	qda_legacy_ops_table.session_destroy(sf->session);
> +	kfree(sf);
> +
> +	return 0;
> +}

[Severity: Critical]
What happens if a DMA-BUF fd returned by qda_shim_alloc_dma_buf() remains open
after the /dev/fastrpc-* file descriptor is closed (which also occurs on
normal process exit when close_files() closes fds in ascending numerical
order)?

When qda_shim_alloc_dma_buf() allocates a buffer via
legacy_alloc_dmabuf() -> qda_gem_create_object() -> qda_memory_manager_alloc(),
a context bank (struct qda_iommu_device) is assigned to
sf->session->assigned_iommu_dev and qda_dma_alloc() stores a raw pointer to it
in gem_obj->iommu_dev:

drivers/accel/qda/qda_memory_dma.c:qda_dma_alloc() {
	...
	gem_obj->virt = virt;
	gem_obj->dma_addr = dma_addr +
			    qda_soc_sid_offset(iommu_dev->qdev->soc_data, iommu_dev->sid);
	gem_obj->iommu_dev = iommu_dev;
	...
}

Closing the /dev/fastrpc-* fd calls qda_shim_release() ->
legacy_session_destroy() -> qda_session_free() ->
qda_memory_manager_release_device(session->assigned_iommu_dev), which clears
iommu_dev->assigned_session = NULL and drops the session's kref on
iommu_dev->ref while the exported DMA-BUF is still allocated and mapped in
that context bank's SMMU domain.

Doesn't that allow another session to be assigned the same context bank while
the buffer is still live, and also lead to a use-after-free of
gem_obj->iommu_dev and gem_obj->iommu_dev->dev in qda_dma_free(),
qda_dma_mmap(), or qda_dma_get_sgtable() if qda_rpmsg_remove() tears down the
context bank devices before the DMA-BUF fd is closed?

> +static long qda_shim_alloc_dma_buf(struct qda_shim_file *sf, void __user *argp)
> +{
> +	struct fastrpc_alloc_dma_buf args;
> +	struct dma_buf *dmabuf;
> +	int fd;
> +
> +	if (copy_from_user(&args, argp, sizeof(args)))
> +		return -EFAULT;
> +
> +	/* The size is 64 bits wide on the wire but a size_t further down */
> +	if (args.size > SIZE_MAX)
> +		return -EINVAL;
> +
> +	/* args.flags is not used, as in drivers/misc/fastrpc.c */
> +	dmabuf = qda_legacy_ops_table.alloc_dmabuf(sf->session, args.size);
> +	if (IS_ERR(dmabuf))
> +		return PTR_ERR(dmabuf);

[Severity: High]
Does mapping the DMA-BUF fd returned here leak the underlying drm_gem_object
and coherent DMA buffer?

qda_shim_alloc_dma_buf() calls legacy_alloc_dmabuf(), which creates a GEM
object with qda_gem_object_funcs and exports it via drm_gem_prime_export():

drivers/accel/qda/qda_gem.c:
const struct drm_gem_object_funcs qda_gem_object_funcs = {
	.free = qda_gem_free_object,
	.get_sg_table = qda_gem_get_sg_table,
	.mmap = qda_gem_mmap_obj,
};

When user space calls mmap() on the returned DMA-BUF fd,
drm_gem_dmabuf_mmap() -> drm_gem_prime_mmap() sets
vma->vm_ops = obj->funcs->vm_ops, calls drm_gem_object_get(obj), and invokes
obj->funcs->mmap(obj, vma). Because qda_gem_object_funcs leaves .vm_ops NULL
(instead of setting drm_gem_vm_open and drm_gem_vm_close) and qda_gem_mmap_obj()
does not set vma->vm_ops either, nothing drops the GEM object reference when
the VMA is unmapped.

[ ... ]

> +static long qda_shim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	struct qda_shim_file *sf = file->private_data;
> +
> +	if (drm_dev_is_unplugged(&sf->session->qda_dev->drm_dev))
> +		return -ENODEV;
> +
> +	return qda_shim_dispatch(sf, cmd, (void __user *)arg);
> +}

[Severity: Critical]
Can qda_shim_ioctl() race with qda_rpmsg_remove() and access freed memory?

drm_dev_is_unplugged() enters the SRCU read-side critical section via
drm_dev_enter() and immediately exits it via drm_dev_exit() before returning
false. As a result, qda_shim_dispatch() -> qda_shim_alloc_dma_buf() ->
legacy_alloc_dmabuf() -> qda_gem_create_object() executes without holding
drm_dev_enter().

If qda_rpmsg_remove() runs concurrently:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_remove() {
	...
	qda_unregister_device(qdev);
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_cb_unpopulate(qdev);
	qda_deinit_device(qdev);
}

misc_deregister() in qda_unregister_device() only serializes against open() and
does not wait for in-flight ioctls on already-open /dev/fastrpc-* files, and
drm_dev_unplug() returns without waiting for qda_shim_alloc_dma_buf() because
the SRCU read lock was already dropped.

qda_cb_unpopulate() and qda_deinit_device() then destroy the context bank
platform devices, free qdev->iommu_mgr->devices, and destroy
qdev->iommu_mgr->assignment_lock, and devres frees qdev->iommu_mgr (which was
allocated with devm_kzalloc(qdev->dev, ...) in qda_init_device()) while the
concurrent qda_shim_alloc_dma_buf() thread is still accessing qdev->iommu_mgr
and allocating from the context bank device.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=9

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

* Re: [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device
  2026-11-01 22:21 ` [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device Ekansh Gupta
@ 2026-11-01 22:43   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:43 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] accel/qda: use-after-free of iommu_dev when DMA-BUF outlives session
- [High] accel/qda: use-after-free in legacy ops after qda_rpmsg_remove()
- [High] accel/qda: GEM object leak on mmap() due to missing vm_ops
- [Low] accel/qda: unnecessary export of qda_session_alloc() and qda_session_free()
--

commit c10272995846f83dffe56b2be6fa2691d606459a
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add legacy ops base and auxiliary device

This commit creates an auxiliary device per DSP domain for the legacy FastRPC
compatibility module to bind to and exports a table of operations
(qda_legacy_ops_table) for session lifecycle and DMA-BUF allocation and
import. Sessions created by the legacy module hold a drm_device reference and
operate on DMA-BUFs and GEM objects directly without a drm_file.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 5e953d1f98bf4..21113deceaadb 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
> @@ -14,7 +15,13 @@
>  #include "qda_ioctl.h"
>  #include "qda_prime.h"
>  
> -static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
> +/**
> + * qda_session_alloc() - Create a session on a device
> + * @qdev: Device to create the session on
> + *
> + * Return: The new session, or ERR_PTR on failure
> + */
> +struct qda_session *qda_session_alloc(struct qda_dev *qdev)
>  {
>  	struct qda_session *session;
>  
> @@ -26,6 +33,7 @@ static struct qda_session *qda_session_alloc(struct qda_dev *qdev)
>  
>  	return session;
>  }
> +EXPORT_SYMBOL_FOR_MODULES(qda_session_alloc, "qda_fastrpc_shim");
>  
>  static int qda_open(struct drm_device *dev, struct drm_file *file)
>  {
> @@ -42,13 +50,18 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
>  	return 0;
>  }
>  
> -static void qda_session_free(struct qda_session *session)
> +/**
> + * qda_session_free() - Release a session and whatever it still holds
> + * @session: Session to release
> + */
> +void qda_session_free(struct qda_session *session)
>  {
>  	if (session->assigned_iommu_dev)
>  		qda_memory_manager_release_device(session->assigned_iommu_dev);
>  
>  	kfree(session);
>  }
> +EXPORT_SYMBOL_FOR_MODULES(qda_session_free, "qda_fastrpc_shim");

[Severity: Low]
This isn't a bug, but why are qda_session_alloc() and qda_session_free()
exported with EXPORT_SYMBOL_FOR_MODULES(..., "qda_fastrpc_shim") in
drivers/accel/qda/qda_drv.c?

Since qda_legacy_ops.o is linked into the same qda module via qda-y in
drivers/accel/qda/Makefile and exposes session creation and destruction
through qda_legacy_ops_table (where legacy_session_create() and
legacy_session_destroy() also manage the drm_dev reference), does
qda_fastrpc_shim ever need to call the raw qda_session_alloc() and
qda_session_free() helpers directly?

[ ... ]

> diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
> new file mode 100644
> index 0000000000000..3566a10c64880
> --- /dev/null
> +++ b/drivers/accel/qda/qda_legacy_ops.c
> @@ -0,0 +1,80 @@

[ ... ]

> +static struct qda_session *legacy_session_create(struct qda_dev *qdev)
> +{
> +	struct qda_session *session;
> +
> +	session = qda_session_alloc(qdev);
> +	if (!IS_ERR(session))
> +		drm_dev_get(&qdev->drm_dev);
> +
> +	return session;
> +}
> +
> +static void legacy_session_destroy(struct qda_session *session)
> +{
> +	struct drm_device *drm_dev = &session->qda_dev->drm_dev;
> +
> +	qda_session_free(session);
> +	drm_dev_put(drm_dev);
> +}

[Severity: High]
What happens if a DMA-BUF allocated by legacy_alloc_dmabuf() (or a GEM object
imported by legacy_import_dmabuf()) outlives the session when
legacy_session_destroy() is called in drivers/accel/qda/qda_legacy_ops.c?
Note that on normal process exit, close_files() closes file descriptors in
ascending order, so an earlier /dev/fastrpc-* fd will be closed before
DMA-BUF fds allocated later from that session.

When legacy_alloc_dmabuf() creates a buffer and exports it via
drm_gem_prime_export(), the resulting dma_buf holds a reference to gem_obj
and qdev->drm_dev, and gem_obj->iommu_dev points to
session->assigned_iommu_dev. However, neither qda_dma_alloc() nor
qda_gem_prime_import_sg_table() takes a reference on iommu_dev->ref or on the
session.

When legacy_session_destroy() runs while the exported DMA-BUF is still alive:

legacy_session_destroy()
  -> qda_session_free()
       -> qda_memory_manager_release_device(session->assigned_iommu_dev)
            iommu_dev->assigned_session = NULL;
            kref_put(&iommu_dev->ref, iommu_device_free);

First, clearing iommu_dev->assigned_session allows assign_available_device()
in drivers/accel/qda/qda_memory_manager.c to hand the same context bank to a
new session while the old session's DMA-BUF remains allocated and mapped in
that context bank's SMMU domain, and causes qda_gem_prime_import() (which
checks to_qda_gem_obj(obj)->iommu_dev == session->assigned_iommu_dev) to
treat the previous session's buffer as belonging to the new session.

Second, if qda_rpmsg_remove() subsequently runs and unregisters the context
bank via qda_cb_unpopulate() -> qda_cb_teardown_device() ->
qda_memory_manager_unregister_device(), iommu_dev->ref drops to zero and
iommu_device_free() frees struct qda_iommu_device. When the DMA-BUF fd is
later closed, drm_gem_dmabuf_release() -> qda_gem_free_object() ->
qda_memory_manager_free() -> qda_dma_free() in
drivers/accel/qda/qda_memory_dma.c dereferences the freed gem_obj->iommu_dev:

void qda_dma_free(struct qda_gem_obj *gem_obj)
{
	dma_free_coherent(gem_obj->iommu_dev->dev, gem_obj->size,
			  gem_obj->virt, qda_gem_dma_addr(gem_obj));

Can this lead to a use-after-free of gem_obj->iommu_dev as well as premature
context bank reuse?

> +
> +/*
> + * 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
> + * legacy interface hands buffers to user space as file descriptors anyway.
> + */
> +static struct dma_buf *legacy_alloc_dmabuf(struct qda_session *session, u64 size)
> +{
> +	struct qda_dev *qdev = session->qda_dev;
> +	struct drm_gem_object *gem_obj;
> +	struct dma_buf *dmabuf;
> +
> +	gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr, size, session);
> +	if (IS_ERR(gem_obj))
> +		return ERR_CAST(gem_obj);
> +
> +	/* O_RDWR so the fd can be mapped writable */
> +	dmabuf = drm_gem_prime_export(gem_obj, O_RDWR);
> +
> +	/* The DMA-BUF took its own reference if the export succeeded */
> +	drm_gem_object_put(gem_obj);
> +
> +	return dmabuf;
> +}

[Severity: High]
Does mmap() on a DMA-BUF returned by legacy_alloc_dmabuf() leak gem_obj and
its backing DMA allocation?

When user space maps the exported DMA-BUF fd, the call chain is:

dma_buf_mmap()
  -> drm_gem_dmabuf_mmap()
       -> drm_gem_prime_mmap()

In drm_gem_prime_mmap():

	vma->vm_ops = obj->funcs->vm_ops;
	drm_gem_object_get(obj);
	ret = obj->funcs->mmap(obj, vma);

However, in drivers/accel/qda/qda_gem.c, qda_gem_object_funcs does not set
.vm_ops:

const struct drm_gem_object_funcs qda_gem_object_funcs = {
	.free = qda_gem_free_object,
	.get_sg_table = qda_gem_get_sg_table,
	.mmap = qda_gem_mmap_obj,
};

And qda_gem_mmap_obj() -> qda_dma_mmap() -> dma_mmap_coherent() does not set
vma->vm_ops either. Because vma->vm_ops remains NULL instead of using
drm_gem_vm_open and drm_gem_vm_close, unmapping the VMA never calls
drm_gem_vm_close() to drop the reference taken by drm_gem_object_get(obj).
Won't every mmap() call permanently leak the qda_gem_obj, its coherent DMA
buffer, and the associated drm_device reference?

[ ... ]

> diff --git a/drivers/accel/qda/qda_prime.c b/drivers/accel/qda/qda_prime.c
> index 7a709e3000d8b..128d4ab2cf64f 100644
> --- a/drivers/accel/qda/qda_prime.c
> +++ b/drivers/accel/qda/qda_prime.c
> @@ -181,4 +181,37 @@ int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_pri
>  	return ret;
>  }
>  
> +/**
> + * qda_prime_import_dmabuf() - Import a DMA-BUF without installing a handle
> + * @drm_dev: DRM device
> + * @session: Session to import into
> + * @dma_buf: Buffer to import
> + *
> + * As qda_prime_fd_to_handle(), but stopping at the GEM object: a session the
> + * legacy shim created has no DRM file to install a handle in. The caller owns
> + * the returned reference.
> + *
> + * Return: The new GEM object on success, ERR_PTR on failure
> + */
> +struct drm_gem_object *qda_prime_import_dmabuf(struct drm_device *drm_dev,
> +					       struct qda_session *session,
> +					       struct dma_buf *dma_buf)
> +{
> +	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
> +	struct drm_gem_object *gem_obj;
> +	int ret;
> +
> +	ret = qda_memory_manager_assign_device(qdev->iommu_mgr, session);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	guard(mutex)(&qdev->import_lock);
> +
> +	qdev->importing_session = session;
> +	gem_obj = qda_gem_prime_import(drm_dev, dma_buf);
> +	qdev->importing_session = NULL;
> +
> +	return gem_obj;
> +}
> +
>  MODULE_IMPORT_NS("DMA_BUF");
> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 2c9d79937b106..14034e250555d 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
> @@ -50,6 +50,9 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
>  {
>  	struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
>  
> +	/* Take the legacy nodes away first, so nothing new opens during teardown */
> +	qda_unregister_device(qdev);
> +
>  	/*
>  	 * Unplug before clearing @rpdev: once there are ioctls that reach the
>  	 * DSP, this is what stops a new one from starting and waits for the

[Severity: High]
In legacy_session_create(), drm_dev_get(&qdev->drm_dev) is taken so that
qdev survives qda_rpmsg_remove() while a legacy session remains open.
However, can legacy_alloc_dmabuf(), legacy_import_dmabuf() (via
qda_prime_import_dmabuf()), or later buffer cleanup trigger a use-after-free
if they run concurrently with or after qda_rpmsg_remove()?

In drivers/accel/qda/qda_rpmsg.c, qda_rpmsg_remove() does:

	qda_unregister_device(qdev);
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_cb_unpopulate(qdev);
	qda_deinit_device(qdev);

1. In qda_init_device(), qdev->iommu_mgr is allocated with
devm_kzalloc(qdev->dev, ...), which is managed by the rpmsg device rather
than &qdev->drm_dev. When qda_rpmsg_remove() finishes, devres frees
qdev->iommu_mgr even though qdev->drm_dev is still held by the open legacy
session.

2. qda_deinit_device() frees iommu_mgr->devices and destroys both
iommu_mgr->assignment_lock and qdev->import_lock, while qda_cb_unpopulate()
calls of_platform_device_destroy() on each context bank child device (which
qda_cb_setup_device() stored in iommu_dev->dev without taking a device
reference).

3. Neither legacy_alloc_dmabuf() nor qda_prime_import_dmabuf() enters a
drm_dev_enter() / drm_dev_exit() critical section, so drm_dev_unplug() does
not synchronize with them. If legacy_alloc_dmabuf() or
qda_prime_import_dmabuf() is called on an already-open legacy session during
or after qda_rpmsg_remove(), won't it dereference the freed qdev->iommu_mgr,
lock the destroyed assignment_lock and import_lock mutexes, and pass the
destroyed iommu_dev->dev to dma_alloc_coherent() or dma_buf_attach()? And
even if buffers were allocated before qda_rpmsg_remove() and only freed
afterwards, won't qda_gem_free_object() pass the destroyed iommu_dev->dev to
dma_free_coherent() or dma_buf_unmap_attachment_unlocked()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=8

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

* Re: [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing
  2026-11-01 22:21 ` [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing Ekansh Gupta
@ 2026-11-01 22:44   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:44 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] accel/qda: union corruption on zero handle in process_dma_handle()
- [High] accel/qda: missing bounds and overflow checks in process_dma_handle()
- [High] accel/qda: dangling session->file pointer after qda_postclose()
- [High] accel/qda: premature drm_dev_put() while qda_session remains alive
- [Medium] accel/qda: unvalidated attr field in qda_fastrpc_prepare_args_invoke()
--

commit 0212e9880ce32dca1d81b275fcea22de7515a72a
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add invocation argument validation and packing

This commit adds FastRPC invocation argument validation, message payload
packing, and per-session message buffer pooling for the QDA driver. It also
converts struct qda_session to be reference-counted so in-flight invocations
can outlive the open file and clean up DSP-released GEM handles.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index bdfb23c3c0b1d..99bd37fca4384 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
[ ... ]
> @@ -50,22 +61,47 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
[ ... ]
>  static void qda_postclose(struct drm_device *dev, struct drm_file *file)
>  {
> -	qda_session_free(file->driver_priv);
> +	qda_session_put(file->driver_priv);
>  }

[Severity: High]
When an invocation outlives the file it was issued on, does session->file
become a dangling pointer to a freed struct drm_file after qda_postclose()
runs?

In qda_open(), session->file is set to the raw struct drm_file pointer, and
qda_fastrpc_prepare_args() takes a reference on ctx->session so that the
session can outlive file closure if the caller stops waiting for the DSP.

However, when the DRM file is closed, drm_file_free() destroys
file->object_idr via drm_gem_release(), calls qda_postclose(), and then
frees struct drm_file with kfree(file):

drm_file_free()
  drm_gem_release(dev, file)
  dev->driver->postclose(dev, file) -> qda_postclose()
    qda_session_put(file->driver_priv)
  kfree(file)

Because qda_postclose() only drops the file owner's session reference without
clearing session->file or synchronizing with in-flight invocations, any
session kept alive by an invocation retains a non-NULL session->file pointer
to freed memory. When the DSP response later triggers
qda_fastrpc_cleanup_handlelist():

void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx)
{
	...
	for (i = 0; i < QDA_FASTRPC_MAX_HANDLELIST; i++) {
		if (!handlelist[i])
			break;
		if (ctx->session->file)
			drm_gem_handle_delete(ctx->session->file, (u32)handlelist[i]);
	}
}

checking ctx->session->file and passing it to drm_gem_handle_delete() will
dereference the freed struct drm_file and its destroyed handle table.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index ec37a38e92158..2971f2f9461fc 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
[ ... ]
> @@ -70,3 +283,272 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
[ ... ]
> +static int process_dma_handle(struct qda_fastrpc_invoke_ctx *ctx, int i,
> +			      union qda_remote_arg *rpra,
> +			      struct qda_phy_page *pages)
> +{
> +	struct drm_gem_object *gem_obj;
> +	int err;
> +
> +	if (!ctx->args[i].handle) {
> +		rpra[i].buf.addr = ctx->args[i].offset;
> +		rpra[i].buf.len = ctx->args[i].length;
> +		return 0;
> +	}

[Severity: High]
What prevents user space from bypassing handle validation in
process_dma_handle() by passing handle == 0 with a non-zero offset for a
DMA-handle argument?

In qda_fastrpc_get_args(), arguments at indices ctx->nbufs <= i <
ctx->nscalars are DMA-handle arguments, which the DSP interprets as struct
qda_remote_dmahandle (rpra[i].dma). Because union qda_remote_arg overlays
struct qda_remote_buf with struct qda_remote_dmahandle:

union qda_remote_arg {
	struct qda_remote_buf buf;
	struct qda_remote_dmahandle dma;
};

writing the 64-bit ctx->args[i].offset into rpra[i].buf.addr when
ctx->args[i].handle is 0 populates rpra[i].dma.handle with the low 32 bits of
ctx->args[i].offset and rpra[i].dma.offset with the high 32 bits, while
rpra[i].buf.len overwrites rpra[i].dma.len.

In addition, qda_fastrpc_get_args() sets list[i].num = ctx->args[i].length ?
1 : 0 before calling process_dma_handle(), so a zero handle with a non-zero
length leaves list[i].num set to 1 while pages[i] remains unpopulated.
Checking !ctx->args[i].handle before ctx->gem_objs[i] also skips any
pre-populated ctx->gem_objs[i] entry when handle is 0.

> +
> +	if (ctx->gem_objs[i]) {
> +		gem_obj = ctx->gem_objs[i];
> +	} else {
> +		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].offset;
> +
> +	return 0;
> +}

[Severity: High]
Should process_dma_handle() validate ctx->args[i].offset and
ctx->args[i].length against the GEM object's size and U32_MAX before casting
them to u32?

In process_msg_buffer(), the user-supplied offset and length are checked
against qda_gem_obj->size so that a descriptor cannot reference memory past
the end of its buffer in the session's context bank:

	if (ctx->args[i].offset > qda_gem_obj->size ||
	    len > qda_gem_obj->size - ctx->args[i].offset) {
		...
		return -EINVAL;
	}

In process_dma_handle(), however, ctx->args[i].length and
ctx->args[i].offset are cast directly to u32 and written into rpra[i].dma.len
and rpra[i].dma.offset without checking that the region lies within
to_qda_gem_obj(gem_obj)->size or fits within 32 bits. Can user space use a
DMA-handle argument to pass a truncated or out-of-bounds region to the DSP?

[ ... ]
> +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 = QDA_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;
> +}

[Severity: Medium]
Should qda_fastrpc_prepare_args_invoke() reject non-zero values in
args[i].attr with -EINVAL?

struct drm_qda_fastrpc_invoke_args defines __u32 attr in the UAPI header,
with no valid attribute flags currently defined, and nothing in
qda_fastrpc_prepare_args_invoke() or qda_fastrpc_get_args() checks that
args[i].attr is zero. If user space passes uninitialized non-zero values in
attr today, won't that prevent future kernel versions from introducing flag
bits in attr without breaking existing user space?

> diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
> index 3566a10c64880..9639ea84b2007 100644
> --- a/drivers/accel/qda/qda_legacy_ops.c
> +++ b/drivers/accel/qda/qda_legacy_ops.c
> @@ -33,7 +33,7 @@ static void legacy_session_destroy(struct qda_session *session)
>  {
>  	struct drm_device *drm_dev = &session->qda_dev->drm_dev;
>  
> -	qda_session_free(session);
> +	qda_session_put(session);
>  	drm_dev_put(drm_dev);
>  }

[Severity: High]
Can dropping the drm_device reference before the last reference to struct
qda_session is released cause a use-after-free of struct qda_dev?

With this change, struct qda_session is reference-counted and can outlive
file closure (both via legacy_session_destroy() and via drm_release() ->
qda_postclose()) when an in-flight invocation or its deferred ctx->put_work
still holds a reference on ctx->session.

However, legacy_session_destroy() still calls drm_dev_put(drm_dev)
immediately after qda_session_put(session), and struct qda_session does not
hold its own drm_dev_get() reference until qda_session_release(). If the
file is closed while an invocation is still in flight and the underlying
rpmsg device is unbound before ctx->put_work completes, the final
drm_device reference can be dropped and struct qda_dev freed before
qda_fastrpc_context_free() and qda_session_release() run:

qda_fastrpc_context_free()
  xa_erase(&ctx->qdev->ctx_xa, ...)
  qda_session_msg_buf_put(ctx->session, &ctx->msg_gem_obj->base)
  qda_session_put(ctx->session) -> qda_session_release()
    qda_session_msg_buf_pool_destroy(session)
      drm_gem_object_put(entry->gem_obj)

At that point, accessing ctx->qdev->ctx_xa and freeing the session's GEM
objects will dereference the already-freed struct qda_dev. Should the
drm_device reference be held across the lifetime of struct qda_session and
released in qda_session_release() instead?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=12

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

* Re: [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts
  2026-11-01 22:21 ` [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts Ekansh Gupta
@ 2026-11-01 22:44   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:44 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] accel/qda: premature publication of ctx in qda_fastrpc_context_alloc()
- [High] accel/qda: premature ctx_xa destruction in qda_deinit_device()
--

commit 0b392f877740b474d80d7f4392edfd3f880836d4
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add FastRPC invocation contexts

This commit adds FastRPC invocation context allocation and reference-counted
cleanup, tracking in-flight invocations per device in an XArray with an 8-bit
context ID and a 48-bit sequence number.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 21113deceaadb..bdfb23c3c0b1d 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
> @@ -100,6 +100,8 @@ struct qda_dev *qda_alloc_device(struct device *dev)
>  
>  	INIT_LIST_HEAD(&qdev->cb_devs);
>  	mutex_init(&qdev->import_lock);
> +	xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
> +	atomic64_set(&qdev->ctx_seq, 0);
>  
>  	return qdev;
>  }
> @@ -122,6 +124,7 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
>  void qda_deinit_device(struct qda_dev *qdev)
>  {
>  	qda_memory_manager_exit(qdev->iommu_mgr);
> +	xa_destroy(&qdev->ctx_xa);
>  	mutex_destroy(&qdev->import_lock);
>  }

[Severity: High]
Can calling xa_destroy(&qdev->ctx_xa) in qda_deinit_device() in
drivers/accel/qda/qda_drv.c race with outstanding invocation contexts that
have not yet dropped their final reference?

In qda_fastrpc_context_alloc() in drivers/accel/qda/qda_fastrpc.c, each
struct qda_fastrpc_invoke_ctx is inserted into qdev->ctx_xa with an initial
caller reference (refcount = 1) and stores a raw pointer ctx->qdev = qdev
without holding a reference on qdev->drm_dev. By design, the entry is only
erased from qdev->ctx_xa in qda_fastrpc_context_free() when the last
reference is released.

When invocations are wired up in the subsequent commits ("accel/qda: Add
RPMsg send and response dispatch" and "accel/qda: Add the invocation ioctl
and teardown on unplug"), qda_rpmsg_send_msg() takes a second reference for
the response path and the caller sleeps in qda_rpmsg_wait_for_rsp() outside
any drm_dev_enter() critical section.

If the rpmsg device is removed while callers are waiting (for example, on a
DSP crash or remoteproc restart), qda_rpmsg_remove() runs drm_dev_unplug(),
qda_fastrpc_flush_pending(qdev), and then immediately qda_deinit_device(qdev).
Because qda_fastrpc_flush_pending() only wakes the waiters and drops the
response reference, the callers still hold their reference (and if
qda_rpmsg_cb() already ran and queued ctx->put_work, ctx is still in
qdev->ctx_xa while put_work is pending, causing qda_fastrpc_flush_pending()
to drop a reference that put_work will also drop).

When qda_deinit_device() immediately calls xa_destroy(&qdev->ctx_xa), the
internal XArray nodes are freed while live contexts remain in the table. When
those woken callers or pending put_work items later run
kref_put(&ctx->refcount, qda_fastrpc_context_free), won't
qda_fastrpc_context_free() call xa_erase(&ctx->qdev->ctx_xa, ...) on an
already-destroyed XArray and dereference ctx->qdev after device teardown?

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> new file mode 100644
> index 0000000000000..14d9da8c20b3f
> --- /dev/null
> +++ b/drivers/accel/qda/qda_fastrpc.c
> @@ -0,0 +1,63 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> +#include <linux/bitfield.h>
> +#include <linux/slab.h>
> +#include "qda_drv.h"
> +#include "qda_fastrpc.h"
> +
> +/**
> + * qda_fastrpc_context_free() - Release an invocation
> + * @ref: Reference counter embedded in the invocation
> + *
> + * Runs when the last reference goes. The invocation leaves the lookup table
> + * here and not earlier, so that its ID cannot be handed to a new invocation
> + * while this one can still be found and completed.
> + */
> +void qda_fastrpc_context_free(struct kref *ref)
> +{
> +	struct qda_fastrpc_invoke_ctx *ctx =
> +		container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);
> +
> +	xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid));
> +	kfree(ctx);
> +}
> +
> +/**
> + * qda_fastrpc_context_alloc() - Allocate an invocation and give it an ID
> + * @qdev: Device the invocation is issued on
> + *
> + * The caller holds the initial reference and drops it with
> + * qda_fastrpc_context_free().
> + *
> + * Return: The new invocation, or ERR_PTR on failure
> + */
> +struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
> +{
> +	struct qda_fastrpc_invoke_ctx *ctx;
> +	u32 id;
> +	int ret;
> +
> +	ctx = kzalloc_obj(*ctx);
> +	if (!ctx)
> +		return ERR_PTR(-ENOMEM);
> +
> +	init_completion(&ctx->work);
> +	kref_init(&ctx->refcount);
> +	ctx->qdev = qdev;
> +	/* Anything but a status the DSP could send, until it answers */
> +	ctx->retval = -1;
> +
> +	/* ID 0 is never used, so a zeroed context in a response matches nothing */
> +	ret = xa_alloc(&qdev->ctx_xa, &id, ctx, XA_LIMIT(1, QDA_FASTRPC_CTXID_MAX),
> +		       GFP_KERNEL);
> +	if (ret) {
> +		kfree(ctx);
> +		return ERR_PTR(ret);
> +	}
> +
> +	ctx->ctxid = FIELD_PREP(QDA_FASTRPC_CTXID_MASK, id) |
> +		     FIELD_PREP(QDA_FASTRPC_CTXID_SEQ_MASK,
> +				(u64)atomic64_inc_return(&qdev->ctx_seq));

[Severity: High]
In qda_fastrpc_context_alloc() in drivers/accel/qda/qda_fastrpc.c, does
calling xa_alloc(&qdev->ctx_xa, &id, ctx, ...) publish ctx into qdev->ctx_xa
and drop xa_lock before ctx->ctxid is initialized?

Because the 8-bit context IDs (1..255) are reused as soon as a prior
invocation is freed, a concurrent lookup on qdev->ctx_xa for that recycled id
(such as qda_rpmsg_cb() handling a late, duplicate, or malformed DSP response,
or qda_fastrpc_flush_pending() during device unplug) can observe ctx while
ctx->ctxid is still 0 or is being written without locking:

1. If a late or malformed response carries sequence number 0 in bits [63:16]
   for slot id, FIELD_GET(QDA_FASTRPC_CTXID_SEQ_MASK, ctx->ctxid) evaluates to
   0 and matches the uninitialized ctx->ctxid, completing ctx->work early and
   scheduling ctx->put_work to drop a reference that qda_rpmsg_send_msg() has
   not yet taken.

2. If qda_fastrpc_context_free() runs while ctx->ctxid is still 0,
   xa_erase(&ctx->qdev->ctx_xa, FIELD_GET(QDA_FASTRPC_CTXID_MASK, ctx->ctxid))
   erases index 0 instead of index id and frees ctx, leaving a dangling
   pointer in qdev->ctx_xa[id].

Should qda_fastrpc_context_alloc() either hold xa_lock(&qdev->ctx_xa) across
__xa_alloc() and the ctx->ctxid assignment, or reserve the slot with NULL
first and store ctx only after ctx->ctxid is initialized?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=10

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

* Re: [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim
  2026-11-01 22:21 ` [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim Ekansh Gupta
@ 2026-11-01 22:47   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:47 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] accel/qda: missing VMA and size checks in process_msg_buffer()
- [High] accel/qda: broken fd and handle semantics in legacy invoke path
- [High] accel/qda: premature unmap of legacy DMA handle args in qda_fastrpc_context_free()
- [High] accel/qda: premature ctx_xa publication in qda_fastrpc_invoke_legacy()
- [High] accel/qda: race with unplug teardown in qda_shim_invoke()
--

commit db6b9ba28a99a54082c1f760f47bf540c628d123
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add invocation through the legacy FastRPC shim

This commit adds FASTRPC_IOCTL_INVOKE support to the legacy FastRPC
compatibility shim by importing each fd-backed argument into the session's
context bank and passing the resolved GEM objects into
qda_fastrpc_invoke_legacy(). Buffer offsets for legacy arguments are
derived from the caller's VMA mapping in process_msg_buffer().

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 3a1959e2c53ee..693dd6f035d2a 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
> @@ -55,6 +55,22 @@ static void setup_pages_from_gem_obj(struct qda_gem_obj *qda_gem_obj,
>  	pages->size = qda_gem_obj->size;
>  }
>  
> +static int calculate_vma_offset(u64 user_ptr, u64 *offset)
> +{
> +	struct vm_area_struct *vma;
> +	int ret = -EFAULT;
> +
> +	mmap_read_lock(current->mm);
> +	vma = find_vma(current->mm, user_ptr);
> +	if (vma && vma->vm_start <= user_ptr) {
> +		*offset = (user_ptr & PAGE_MASK) - vma->vm_start;
> +		ret = 0;
> +	}
> +	mmap_read_unlock(current->mm);
> +
> +	return ret;
> +}
> +
>  static u64 calculate_page_aligned_size(u64 addr, u64 len)
>  {
>  	u64 pg_start = (addr & PAGE_MASK) >> PAGE_SHIFT;
> @@ -286,23 +302,45 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
>  }
>  
>  /*
> - * Resolve the address of an in/out buffer argument. The buffer is named by a
> - * GEM handle, looked up in the calling session's own handle table, so a
> - * session cannot name another's buffer. The offset and length come from user
> - * space and are checked against the buffer's real size: a region described as
> - * larger than its buffer would let the DSP read and write past the end of it,
> - * into whatever else is mapped in the same context bank.
> + * Resolve the address of an in/out buffer argument. The buffer is either a
> + * GEM object the legacy shim already resolved into ctx->gem_objs[i], or a GEM
> + * handle looked up in the calling session's own handle table. For a handle,
> + * the offset and length come from user space and are checked against the
> + * buffer's real size: a region described as larger than its buffer would let
> + * the DSP read and write past the end of it, into whatever else is mapped in
> + * the same context bank.
>   */
>  static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
>  			      union qda_remote_arg *rpra,
>  			      struct qda_phy_page *pages)
>  {
>  	u64 len = ctx->args[i].length;
> -	struct qda_gem_obj *qda_gem_obj;
>  	struct drm_gem_object *gem_obj;
> +	struct qda_gem_obj *qda_gem_obj;
>  	u64 unaligned_addr;
> +	u64 vma_offset = 0;
>  	int err;
>  
> +	if (ctx->gem_objs[i]) {
> +		/*
> +		 * The legacy ABI predates GEM handles, so the offset field
> +		 * holds an address in the caller's own mapping of the buffer,
> +		 * or 0 if it has none, meaning the start of the buffer.
> +		 */
> +		qda_gem_obj = to_qda_gem_obj(ctx->gem_objs[i]);
> +		if (ctx->args[i].offset) {
> +			err = calculate_vma_offset(ctx->args[i].offset, &vma_offset);
> +			if (err)
> +				return err;
> +		}
> +
> +		rpra[i].buf.addr = ctx->args[i].offset;
> +		pages[i].addr = qda_gem_obj->dma_addr + vma_offset;
> +		pages[i].size = calculate_page_aligned_size(ctx->args[i].offset, len);
> +
> +		return 0;
> +	}

[Severity: Critical]
Could this legacy buffer path in process_msg_buffer() in
drivers/accel/qda/qda_fastrpc.c allow out-of-bounds DSP DMA access or
Stream ID spoofing across context banks?

When qda_shim_invoke() -> qda_fastrpc_invoke_legacy() ->
qda_fastrpc_invoke_common() -> qda_fastrpc_invoke_pack() ->
qda_fastrpc_get_args() -> process_msg_buffer() resolves a buffer with
ctx->gem_objs[i] set, calculate_vma_offset() looks up any VMA in
current->mm containing user_ptr and computes:

    *offset = (user_ptr & PAGE_MASK) - vma->vm_start;

without verifying that the VMA backs the imported DMA-BUF for
ctx->gem_objs[i] or accounting for vma->vm_pgoff.

Unlike the GEM handle path below it, which checks:

    if (ctx->args[i].offset > qda_gem_obj->size ||
        len > qda_gem_obj->size - ctx->args[i].offset)

and unlike fastrpc_map_attach() in drivers/misc/fastrpc.c, which checks
len > map->size, the legacy branch in process_msg_buffer() does not check
vma_offset, the in-page offset (ctx->args[i].offset & ~PAGE_MASK), or len
against qda_gem_obj->size.

As a result, passing a length or VMA offset larger than qda_gem_obj->size
sets pages[i].addr and pages[i].size to a range extending past the end of
qda_gem_obj into adjacent mappings in the session's IOMMU context bank,
such as ctx->msg_gem_obj. In addition, because qda_gem_obj->dma_addr
encodes the session's Stream ID at soc_data->sid_pos (bit 32 on
default_soc_data), passing a user_ptr inside a large anonymous VMA so that
vma_offset >= (1ULL << 32) causes qda_gem_obj->dma_addr + vma_offset to
carry into the Stream ID bits and target another session's context bank.

Finally, because ctx->args[i].offset and len are unvalidated 64-bit values
on this path, addr + len - 1 and (pg_end - pg_start + 1) * PAGE_SIZE in
calculate_page_aligned_size() can overflow 64-bit arithmetic and wrap
pages[i].size to a small value or zero.

Should calculate_vma_offset() and process_msg_buffer() verify that the
offset and length stay within qda_gem_obj->size without overflowing?

[ ... ]

> @@ -551,6 +589,105 @@ int qda_fastrpc_invoke_common(struct qda_dev *qdev, struct qda_fastrpc_invoke_ct
>  	return 0;
>  }
>  
> +/**
> + * qda_fastrpc_invoke_legacy() - Perform a dynamic invocation on behalf of the legacy shim
> + * @qdev: Device to invoke on
> + * @session: Legacy session (no backing drm_file)
> + * @handle: Remote handle to invoke on the DSP
> + * @sc: FastRPC scalars value encoding the number of in/out buffers
> + * @legacy_args: Kernel-side argument array, one entry per QDA_REMOTE_SCALARS_LENGTH(sc)
> + *
> + * Unlike the accel UAPI path, the argument descriptors and any backing GEM
> + * objects already live in kernel memory: the shim has already resolved
> + * every fd-backed argument via its own import path, since a legacy session
> + * has no GEM handle table to resolve a handle-based argument against.
> + * Ownership of each @legacy_args[i].obj reference transfers to this function,
> + * on failure as well as on success.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_fastrpc_invoke_legacy(struct qda_dev *qdev, struct qda_session *session,
> +			      u32 handle, u32 sc, struct qda_legacy_invoke_arg *legacy_args)
> +{
> +	struct qda_fastrpc_invoke_ctx *ctx;
> +	struct drm_qda_fastrpc_invoke_args *args;
> +	u32 nscalars;
> +	int err;
> +	int i;
> +
> +	nscalars = QDA_REMOTE_SCALARS_LENGTH(sc);
> +
> +	/*
> +	 * An fd naming a buffer another session exported resolves to the
> +	 * original object, still mapped in that session's context bank, so
> +	 * the same check as for a handle applies.
> +	 */
> +	for (i = 0; i < nscalars; ++i) {
> +		if (legacy_args[i].obj &&
> +		    to_qda_gem_obj(legacy_args[i].obj)->iommu_dev != session->assigned_iommu_dev) {
> +			err = -EACCES;
> +			goto err_put_legacy_objs;
> +		}
> +	}
> +
> +	ctx = qda_fastrpc_context_alloc(qdev);
> +	if (IS_ERR(ctx)) {
> +		err = PTR_ERR(ctx);
> +		goto err_put_legacy_objs;
> +	}
> +
> +	/* Dropped by qda_fastrpc_context_free(), as for the accel UAPI path */
> +	qda_session_get(session);
> +	ctx->session = session;
> +	ctx->handle = handle;
> +	ctx->sc = sc;
> +
> +	ctx->nbufs = QDA_REMOTE_SCALARS_INBUFS(sc) + QDA_REMOTE_SCALARS_OUTBUFS(sc);
> +	if (nscalars) {
> +		args = kzalloc_objs(*args, nscalars);
> +		if (!args) {
> +			err = -ENOMEM;
> +			goto err_put_legacy_objs_ctx;
> +		}
> +
> +		ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
> +		if (!ctx->gem_objs) {
> +			kfree(args);
> +			err = -ENOMEM;
> +			goto err_put_legacy_objs_ctx;
> +		}
> +
> +		for (i = 0; i < nscalars; ++i) {
> +			args[i].offset = legacy_args[i].ptr;
> +			args[i].length = legacy_args[i].length;
> +			/* process_dma_handle() skips handle args whose handle is 0 */
> +			args[i].handle = (i >= ctx->nbufs && legacy_args[i].obj) ?
> +					 (u32)legacy_args[i].fd : 0;
> +			ctx->gem_objs[i] = legacy_args[i].obj;
> +		}

[Severity: High]
Won't moving the sole reference to legacy_args[i].obj into ctx->gem_objs[i]
for DMA handle arguments (i >= ctx->nbufs) in qda_fastrpc_invoke_legacy()
cause them to be unmapped from the SMMU context bank as soon as the
invocation finishes?

In qda_shim_invoke(), shim_import_fd() imports each fd via
qda_prime_import_dmabuf(), returning a drm_gem_object with a single
reference for any external DMA-BUF (or once user space closes its fd).
When the invocation finishes, qda_fastrpc_context_free() drops every entry
in ctx->gem_objs:

    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);
    }

Unlike drivers/misc/fastrpc.c (where fastrpc_create_maps() retains handle
mappings on fl->maps across invocations until the DSP returns their fd in
fdlist) and unlike the accel UAPI path (where ctx->session->file holds a
GEM handle reference until qda_fastrpc_cleanup_handlelist() deletes it),
qda_fastrpc_context_free() drops the last reference on DMA handle objects
immediately on return, unmapping them while the DSP still holds their IOVA.
Moreover, qda_fastrpc_cleanup_handlelist() checks if (ctx->session->file),
which is always NULL for legacy sessions.

How are DMA handle mappings meant to stay alive across invocations and be
released via handlelist for legacy sessions?

> +
> +		ctx->args = args;
> +	}
> +
> +	ctx->nscalars = nscalars;
> +
> +	err = qda_fastrpc_invoke_common(qdev, ctx);
> +
> +	kref_put(&ctx->refcount, qda_fastrpc_context_free);

[Severity: High]
Can a concurrent call to qda_rpmsg_remove() -> qda_fastrpc_flush_pending()
drop the only reference on ctx and cause a use-after-free and double put in
qda_fastrpc_invoke_legacy()?

In qda_fastrpc_invoke_legacy(), qda_fastrpc_context_alloc() initializes
ctx->refcount to 1 and immediately inserts ctx into qdev->ctx_xa via
xa_alloc(), before ctx->session, ctx->args, and ctx->gem_objs are populated
and before qda_rpmsg_send_msg() takes the second reference for the response
path.

Because qda_shim_ioctl() only performs a lockless drm_dev_is_unplugged()
check rather than holding a drm_dev_enter() section across the ioctl,
qda_rpmsg_remove() can run concurrently and call
qda_fastrpc_flush_pending():

    xa_for_each(&qdev->ctx_xa, idx, ctx) {
        ctx->retval = -EPIPE;
        complete(&ctx->work);
        kref_put(&ctx->refcount, qda_fastrpc_context_free);
    }

If qda_fastrpc_flush_pending() runs before qda_rpmsg_send_msg() acquires
the second reference (or after qda_rpmsg_send_msg() fails with -ENODEV), it
drops ctx->refcount from 1 to 0 and frees ctx while
qda_fastrpc_invoke_legacy() is still using it, followed by a second
kref_put() on the freed ctx when qda_fastrpc_invoke_legacy() returns.

[ ... ]

> diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
> index 25f5c242efde1..03fce990ccdb9 100644
> --- a/drivers/accel/qda/qda_fastrpc_shim.c
> +++ b/drivers/accel/qda/qda_fastrpc_shim.c

[ ... ]

> @@ -50,6 +52,25 @@ struct qda_shim_dev {
>  
>  static const struct file_operations qda_shim_fops;
>  
> +/* A negative @fd means the argument has no buffer, which is not an error */
> +static struct drm_gem_object *shim_import_fd(struct qda_session *session, s32 fd)
> +{
> +	struct drm_gem_object *obj;
> +	struct dma_buf *dmabuf;
> +
> +	if (fd < 0)
> +		return NULL;
> +
> +	dmabuf = dma_buf_get(fd);
> +	if (IS_ERR(dmabuf))
> +		return ERR_CAST(dmabuf);
> +
> +	obj = qda_legacy_ops_table.import_dmabuf(session, dmabuf);
> +	dma_buf_put(dmabuf);
> +
> +	return obj;
> +}
> +
>  static int qda_shim_open(struct inode *inode, struct file *file)
>  {
>  	struct qda_shim_node *snode = container_of(file->private_data, struct qda_shim_node,
> @@ -120,11 +141,77 @@ static long qda_shim_alloc_dma_buf(struct qda_shim_file *sf, void __user *argp)
>  	return 0;
>  }
>  
> +static long qda_shim_invoke(struct qda_shim_file *sf, void __user *argp)
> +{
> +	struct qda_legacy_invoke_arg *largs;
> +	struct fastrpc_invoke_args *uargs;
> +	struct fastrpc_invoke args;
> +	u32 nargs;
> +	int err;
> +	int i;
> +
> +	if (copy_from_user(&args, argp, sizeof(args)))
> +		return -EFAULT;
> +
> +	nargs = QDA_REMOTE_SCALARS_LENGTH(args.sc);
> +	if (!nargs)
> +		return qda_legacy_ops_table.invoke(sf->session, args.handle, args.sc, NULL);
> +
> +	uargs = kcalloc(nargs, sizeof(*uargs), GFP_KERNEL);
> +	if (!uargs)
> +		return -ENOMEM;
> +
> +	if (copy_from_user(uargs, u64_to_user_ptr(args.args), nargs * sizeof(*uargs))) {
> +		kfree(uargs);
> +		return -EFAULT;
> +	}
> +
> +	largs = kcalloc(nargs, sizeof(*largs), GFP_KERNEL);
> +	if (!largs) {
> +		kfree(uargs);
> +		return -ENOMEM;
> +	}
> +
> +	for (i = 0; i < nargs; ++i) {
> +		struct drm_gem_object *obj;
> +
> +		largs[i].ptr = uargs[i].ptr;
> +		largs[i].length = uargs[i].length;
> +		largs[i].fd = uargs[i].fd;
> +
> +		obj = shim_import_fd(sf->session, uargs[i].fd);
> +		if (IS_ERR(obj)) {
> +			err = PTR_ERR(obj);
> +			goto err_put_objs;
> +		}
> +		largs[i].obj = obj;
> +	}

[Severity: High]
Does the fd handling across shim_import_fd(), qda_fastrpc_invoke_legacy(),
process_msg_buffer(), and process_dma_handle() break several valid legacy
FASTRPC_IOCTL_INVOKE argument patterns?

1. In drivers/misc/fastrpc.c, fastrpc_create_maps() skips importing
   arguments when fd == 0, fd == -1, or length == 0. Here, qda_shim_invoke()
   calls shim_import_fd() even when uargs[i].length == 0, and
   shim_import_fd() only returns NULL for fd < 0. If user space passes a
   zero-initialized descriptor (fd == 0, length == 0) or fd == 0 for an
   unbacked argument, shim_import_fd() calls dma_buf_get(0) (typically
   stdin) and fails the ioctl with -EINVAL or -EBADF.

2. Conversely, if a DMA handle argument (i >= ctx->nbufs) genuinely uses
   fd == 0 and shim_import_fd() imports it into legacy_args[i].obj,
   qda_fastrpc_invoke_legacy() sets:

       args[i].handle = (i >= ctx->nbufs && legacy_args[i].obj) ?
                        (u32)legacy_args[i].fd : 0;

   which evaluates to 0. In process_dma_handle(), the check for
   !ctx->args[i].handle runs before checking ctx->gem_objs[i], so fd 0 is
   treated as having no buffer and setup_pages_from_gem_obj() is skipped.

3. For a buffer argument (i < ctx->nbufs) with fd < 0 and length > 0,
   shim_import_fd() returns NULL ("not an error"), leaving
   ctx->gem_objs[i] == NULL and ctx->args[i].handle == 0. When
   qda_fastrpc_get_args() calls process_msg_buffer(), both
   ctx->gem_objs[i] and ctx->args[i].handle are 0, so process_msg_buffer()
   immediately returns -EINVAL.

> +
> +	kfree(uargs);
> +
> +	/* Takes ownership of every largs[i].obj, whether or not it succeeds */
> +	err = qda_legacy_ops_table.invoke(sf->session, args.handle, args.sc, largs);

[Severity: High]
What prevents qda_rpmsg_remove() from tearing down the device while
qda_shim_invoke() is importing buffers and building the invocation in
drivers/accel/qda/qda_fastrpc_shim.c?

In qda_shim_ioctl(), the unplug check is a lockless call to
drm_dev_is_unplugged(&sf->session->qda_dev->drm_dev), and drm_dev_enter()
is not acquired until qda_rpmsg_send_msg().

If qda_rpmsg_remove() runs right after qda_shim_ioctl() passes
drm_dev_is_unplugged(), drm_dev_unplug() completes immediately without
waiting for in-flight shim ioctls, and qda_rpmsg_remove() proceeds to run:

    qda_fastrpc_flush_pending(qdev);
    qda_cb_unpopulate(qdev);
    qda_deinit_device(qdev);

This destroys the context bank platform devices (of_platform_device_destroy),
frees qdev->iommu_mgr->devices, and destroys assignment_lock,
qdev->ctx_xa, and qdev->import_lock while qda_shim_invoke() ->
shim_import_fd() -> qda_prime_import_dmabuf() and
qda_fastrpc_invoke_legacy() are concurrently accessing them.

Should qda_shim_ioctl() guard the dispatch call with drm_dev_enter() and
drm_dev_exit() so that drm_dev_unplug() waits for in-flight legacy ioctls
to finish before tearing down the device?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=14

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

* Re: [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim
  2026-11-01 22:21 ` [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim Ekansh Gupta
@ 2026-11-01 22:48   ` sashiko-bot
  2026-11-02  8:47   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:48 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] accel/qda: TOCTOU race and UAF between legacy_release() and qda_rpmsg_remove()
- [High] accel/qda: missing QDA_FASTRPC_INIT_HANDLE check on user invoke paths
- [Medium] accel/qda: qda_shim_init_create() imports args.filefd when filelen or filefd is 0
--

commit cd276e39d68d699c859df87ba50e8e071d334588
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add DSP process creation and release to the legacy shim

This commit adds DSP process creation (FASTRPC_IOCTL_INIT_CREATE) and release
upon file close to the legacy /dev/fastrpc-* compatibility shim, enforcing
secure/non-secure node rules and importing the ELF image file descriptor into
the session's context bank.

> diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
> index 03fce990ccdb9..4a2ba20ee01ac 100644
> --- a/drivers/accel/qda/qda_fastrpc_shim.c
> +++ b/drivers/accel/qda/qda_fastrpc_shim.c
[ ... ]
> @@ -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;

[Severity: High]
Can user space bypass both qda_shim_session_rejected() here and the
CAP_SYS_ADMIN check in qda_fastrpc_prepare_args_init_create() by sending a
QDA_FASTRPC_INIT_HANDLE (0x1) message through FASTRPC_IOCTL_INVOKE or
DRM_IOCTL_QDA_REMOTE_INVOKE?

In drivers/misc/fastrpc.c, fastrpc_internal_invoke() rejects user-space
invocations targeting FASTRPC_INIT_HANDLE with -EPERM when !kernel. Here,
however, neither qda_shim_invoke() -> legacy_invoke() ->
qda_fastrpc_invoke_legacy() nor qda_ioctl_invoke() ->
qda_fastrpc_prepare_args_invoke() blocks handle == QDA_FASTRPC_INIT_HANDLE.

Even more concerning, qda_fastrpc_invoke_pack() checks ctx->handle rather than
ctx->type:

    if (ctx->handle == QDA_FASTRPC_INIT_HANDLE)
        msg->fastrpc.remote_session_id = 0;
    else
        msg->fastrpc.remote_session_id = ctx->session->remote_session_id;

As a result, an unprivileged client on a non-secure node can call
FASTRPC_IOCTL_INIT_CREATE with FASTRPC_MODE_UNSIGNED_MODULE to transition
session->pd to QDA_USER_PD (or leave session->pd at QDA_ROOT_PD) and then issue
FASTRPC_IOCTL_INVOKE with handle = QDA_FASTRPC_INIT_HANDLE and sc encoding
QDA_FASTRPC_RMID_INIT_CREATE, QDA_FASTRPC_RMID_INIT_CREATE_ATTR, or
QDA_FASTRPC_RMID_INIT_RELEASE using user-controlled buffers. Should
QDA_FASTRPC_INIT_HANDLE be rejected on the user-space invoke paths?

> +
> +	elf_obj = shim_import_fd(sf->session, args.filefd);
> +	if (IS_ERR(elf_obj))
> +		return PTR_ERR(elf_obj);

[Severity: Medium]
What happens in qda_shim_init_create() when user space passes a
zero-initialized args.filefd (0) for an empty image (args.filelen == 0) or for
a bare-pointer image (args.file != 0, args.filelen > 0), or when args.filelen
is 0 with a non-negative args.filefd?

In drivers/misc/fastrpc.c, fastrpc_init_create_process() only imports
init.filefd when both init.filelen and init.filefd are non-zero:

    if (init.filelen && init.filefd) {
        ...
    }

And in drivers/accel/qda/qda_legacy_ops.c, legacy_init_create() is written to
expect elf_obj == NULL when no descriptor-backed image is used:

    if (!elf_obj && filelen)
        return -EOPNOTSUPP;

However, shim_import_fd() only treats negative file descriptors as absent:

    if (fd < 0)
        return NULL;

    dmabuf = dma_buf_get(fd);

If a caller zero-initializes struct fastrpc_init_create so args.filefd is 0,
shim_import_fd(sf->session, 0) calls dma_buf_get(0) on standard input, failing
with -EINVAL or -EBADF instead of allowing an empty image or returning
-EOPNOTSUPP for a bare pointer (or silently importing fd 0 if stdin happens to
be a valid DMA-BUF). Should qda_shim_init_create() only call shim_import_fd()
when args.filelen && args.filefd (or should shim_import_fd() treat fd <= 0 as
absent)?

> diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
> index 74ee4995350e2..2b3b3f4fb246b 100644
> --- a/drivers/accel/qda/qda_legacy_ops.c
> +++ b/drivers/accel/qda/qda_legacy_ops.c
[ ... ]
> @@ -38,6 +39,42 @@ static void legacy_session_destroy(struct qda_session *session)
>  	drm_dev_put(drm_dev);
>  }
[ ... ]
> +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);
> +}

[Severity: High]
Does using drm_dev_is_unplugged() instead of holding a critical section across
qda_release_dsp_process() expose a race and use-after-free with
qda_rpmsg_remove()?

In drivers/accel/qda/qda_rpmsg.c, qda_rpmsg_remove() relies on
drm_dev_unplug() to wait for active callers to finish before flushing pending
contexts and tearing down device resources:

    drm_dev_unplug(&qdev->drm_dev);
    qdev->rpdev = NULL;
    qda_fastrpc_flush_pending(qdev);
    qda_cb_unpopulate(qdev);
    qda_deinit_device(qdev);

Because drm_dev_is_unplugged() immediately drops its SRCU read lock via
drm_dev_exit(), qda_rpmsg_remove() can run concurrently right after
drm_dev_is_unplugged() returns false:

1. In qda_release_dsp_process() -> qda_fastrpc_invoke(),
   qda_fastrpc_context_alloc(qdev) allocates ctx with refcount == 1 and stores
   it in qdev->ctx_xa.
2. Concurrently, qda_rpmsg_remove() calls drm_dev_unplug(&qdev->drm_dev).
   When qda_fastrpc_invoke_common() calls qda_rpmsg_send_msg(), drm_dev_enter()
   now fails with -ENODEV, so the second reference on ctx for the response
   callback is never taken.
3. Meanwhile, qda_fastrpc_flush_pending(qdev) in qda_rpmsg_remove() iterates
   qdev->ctx_xa and calls kref_put(&ctx->refcount, qda_fastrpc_context_free),
   dropping the caller's sole reference and freeing ctx while
   qda_fastrpc_invoke() is still using it and about to call
   kref_put(&ctx->refcount, qda_fastrpc_context_free) a second time at out_put.
4. Furthermore, qda_rpmsg_remove() proceeds to qda_cb_unpopulate(qdev) and
   qda_deinit_device(qdev) (destroying the context-bank platform devices,
   qdev->iommu_mgr, and qdev->ctx_xa) while qda_release_dsp_process() is still
   allocating from qdev->ctx_xa and qdev->iommu_mgr.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=16

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

* Re: [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains
  2026-11-01 22:21 ` [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains Ekansh Gupta
@ 2026-11-01 22:49   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:49 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] accel/qda: race between qda_ioctl_attach() and qda_ioctl_init_create()
--

commit c03ef3a6851102dcdbec94040ce6c4949c48a5b9
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add remote attach to root and sensors protection domains

This commit adds an ioctl to attach a calling session to an existing root or
sensors protection domain on the DSP instead of creating a new user process.

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index 999afa9e70c23..9b8cccfea1e3a 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -121,6 +121,8 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
>  	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
>  		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
>  		WRITE_ONCE(session->pd, QDA_USER_PD);
> +	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH) {
> +		WRITE_ONCE(session->pd, ctx->pd);
>  	}
>  
>  out_put:
> @@ -174,6 +176,40 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
>  	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
>  }
>  
> +/**
> + * qda_ioctl_attach() - Attach to a DSP-resident protection domain
> + * @dev: DRM device structure
> + * @data: User-space data (struct drm_qda_remote_attach)
> + * @file_priv: DRM file private data
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv)
> +{
> +	struct drm_qda_remote_attach *args = data;
> +	struct qda_session *session = file_priv->driver_priv;
> +	int pd;
> +
> +	if (args->pad)
> +		return -EINVAL;
> +
> +	switch (args->pd_type) {
> +	case QDA_ATTACH_ROOT_PD:
> +		pd = QDA_ROOT_PD;
> +		break;
> +	case QDA_ATTACH_SENSORS_PD:
> +		pd = QDA_SENSORS_PD;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (READ_ONCE(session->init_mem_gem_obj))
> +		return -EBUSY;
> +
> +	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
> +}

[Severity: High]
Can a concurrent call to qda_ioctl_init_create() race with qda_ioctl_attach()
on the same file descriptor and corrupt session->pd?

As noted in the struct qda_session definition for init_mem_gem_obj, ioctls on
a single file can run concurrently. In qda_ioctl_attach(),
session->init_mem_gem_obj is checked without holding a lock before calling
qda_fastrpc_invoke(), which sleeps in qda_fastrpc_invoke_common() while
waiting for the DSP response.

Meanwhile, qda_ioctl_init_create() -> qda_session_init_create() ->
qda_fastrpc_invoke() only populates session->init_mem_gem_obj via
qda_session_set_init_mem() after its own DSP invocation completes:

	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
		WRITE_ONCE(session->pd, QDA_USER_PD);
	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH) {
		WRITE_ONCE(session->pd, ctx->pd);
	}

If thread 1 runs qda_ioctl_init_create() and thread 2 runs qda_ioctl_attach()
before thread 1's RPC finishes, thread 2 sees session->init_mem_gem_obj as
null and proceeds. If thread 1 finishes first and sets
session->init_mem_gem_obj and session->pd, and thread 2 finishes second,
thread 2 will overwrite session->pd with the attached root or sensors domain
(or conversely, qda_ioctl_init_create() can be called on an already-attached
session).

When the file is later closed, qda_postclose() checks
session->init_mem_gem_obj and sends the process release request to
session->pd:

drivers/accel/qda/qda_drv.c:qda_postclose() {
	if (READ_ONCE(session->init_mem_gem_obj) && drm_dev_enter(dev, &idx)) {
		qda_release_dsp_process(session->qda_dev, session);
		drm_dev_exit(idx);
	}

	qda_session_put(session);
}

Because qda_fastrpc_prepare_args() sets ctx->pd from session->pd, wouldn't
qda_release_dsp_process() send the release message to the wrong protection
domain, leaving the user process running on the DSP while qda_session_put()
frees session->init_mem_gem_obj?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=20

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

* Re: [PATCH v4 18/25] accel/qda: Add remote memory unmapping from the DSP address space
  2026-11-01 22:21 ` [PATCH v4 18/25] accel/qda: Add remote memory unmapping from " Ekansh Gupta
@ 2026-11-01 22:49   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:49 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] accel/qda: missing GEM object reference for active DSP mappings
- [High] accel/qda: refcount underflow and UAF in qda_fastrpc_flush_pending()
- [Low] accel/qda: duplicate address fields and unchecked fields in qda_ioctl_munmap()
--

commit 52a28fd51f1253e9ef94a6c86442ba00197bb35c
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add remote memory unmapping from the DSP address space

This commit adds the DRM_IOCTL_QDA_REMOTE_MUNMAP ioctl and FastRPC prepare
handlers to allow user space to unmap plain and attribute-based buffer
mappings from the DSP address space.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 744d7317cbe11..dbd14cd07cb20 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
> @@ -1072,6 +1072,73 @@ static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx,
>  	return err;
>  }
>  
> +/*
> + * INIT_MUNMAP packed layout:
> + *   [0] req	sizeof(qda_fastrpc_munmap_req_msg)	IN
> + */
> +static int qda_fastrpc_prepare_args_munmap(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
> +{
> +	struct drm_qda_mem_unmap *uargs = argp;
> +	struct qda_fastrpc_munmap_req_msg *req;
> +	struct qda_gem_obj *kernel_gem;
> +	int err;
> +
> +	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MUNMAP, 1, 0);
> +	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
> +
> +	err = alloc_kernel_args(ctx, 1);
> +	if (err)
> +		return err;
> +
> +	kernel_gem = alloc_kernel_gem(ctx, sizeof(*req));
> +	if (IS_ERR(kernel_gem))
> +		return PTR_ERR(kernel_gem);
> +
> +	req = kernel_gem->virt;
> +	req->remote_session_id = ctx->session->remote_session_id;
> +	req->vaddr = uargs->vaddrout;
> +	req->size = uargs->size;
> +
> +	assign_kernel_gem_arg(ctx, 0, kernel_gem, req, sizeof(*req));
> +	drm_gem_object_put(&kernel_gem->base);
> +
> +	return 0;
> +}
> +
> +/*
> + * INIT_MEM_UNMAP packed layout:
> + *   [0] req	sizeof(qda_fastrpc_mem_unmap_req_msg)	IN
> + */
> +static int qda_fastrpc_prepare_args_mem_unmap(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
> +{
> +	struct drm_qda_mem_unmap *uargs = argp;
> +	struct qda_fastrpc_mem_unmap_req_msg *req;
> +	struct qda_gem_obj *kernel_gem;
> +	int err;
> +
> +	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, 1, 0);
> +	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
> +
> +	err = alloc_kernel_args(ctx, 1);
> +	if (err)
> +		return err;
> +
> +	kernel_gem = alloc_kernel_gem(ctx, sizeof(*req));
> +	if (IS_ERR(kernel_gem))
> +		return PTR_ERR(kernel_gem);
> +
> +	req = kernel_gem->virt;
> +	req->remote_session_id = ctx->session->remote_session_id;
> +	req->handle = uargs->dsp_handle;
> +	req->vaddrin = uargs->vaddr;
> +	req->len = uargs->size;
> +
> +	assign_kernel_gem_arg(ctx, 0, kernel_gem, req, sizeof(*req));
> +	drm_gem_object_put(&kernel_gem->base);
> +
> +	return 0;
> +}

[Severity: High]
Can leaving the GEM object lifetime entirely to user space while a DSP
mapping is active lead to DMA use-after-free?

During qda_ioctl_mmap(), setup_mmap_pages() acquires ctx->map_obj only for
the duration of the MMAP RPC, and qda_fastrpc_context_free() drops it as
soon as the ioctl completes:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_free() {
	...
	if (ctx->map_obj)
		drm_gem_object_put(ctx->map_obj);
	...
}

While the mapping remains live on the DSP between DRM_IOCTL_QDA_REMOTE_MMAP
and DRM_IOCTL_QDA_REMOTE_MUNMAP, the kernel does not track the mapping on
struct qda_session or hold a drm_gem_object reference.

If user space closes the GEM handle via DRM_IOCTL_GEM_CLOSE before calling
DRM_IOCTL_QDA_REMOTE_MUNMAP (or if qda_rpmsg_wait_for_rsp() is interrupted
by a signal after the map request was sent so user space never receives
vaddrout), qda_gem_free_object() immediately frees the DMA buffer and its
IOVA in session->assigned_iommu_dev while the DSP mapping is still active.

Because session->assigned_iommu_dev is also used by alloc_kernel_gem() and
qda_fastrpc_invoke_pack() to allocate internal FastRPC request and payload
buffers, can the freed IOVA be reused for kernel RPC messages while an
unprivileged DSP user process still has vaddrout mapped to it?

Furthermore, even if user space keeps the handle open, when a process exits
or is killed while a mapping is active, drm_file_free() runs
drm_gem_release() before dev->driver->postclose() (qda_postclose()):

drivers/gpu/drm/drm_file.c:drm_file_free()
  -> drm_gem_release()
  -> qda_postclose()
       -> qda_release_dsp_process()

Doesn't this free and unmap all GEM buffers while the DSP process is still
running and before qda_release_dsp_process() tears it down? Also, because
active mappings are not tracked by the kernel,
qda_fastrpc_prepare_args_munmap() and qda_fastrpc_prepare_args_mem_unmap()
forward arbitrary user-supplied vaddr/vaddrout, size, and dsp_handle values
to QDA_FASTRPC_INIT_HANDLE without verifying that they match an existing
mapping.

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index cd3cea72f50e8..17d1b488b4a9b 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -197,6 +197,30 @@ int qda_ioctl_mmap(struct drm_device *dev, void *data, struct drm_file *file_pri
>  	}
>  }
>  
> +/**
> + * qda_ioctl_munmap() - Remove a buffer's mapping from the DSP's address space
> + * @dev: DRM device structure
> + * @data: User-space data (struct drm_qda_mem_unmap)
> + * @file_priv: DRM file private data
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_ioctl_munmap(struct drm_device *dev, void *data, struct drm_file *file_priv)
> +{
> +	struct drm_qda_mem_unmap *unmap_req = data;
> +
> +	switch (unmap_req->request) {
> +	case QDA_REQUEST_MUNMAP:
> +		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MUNMAP, data,
> +					  file_priv->driver_priv);
> +	case QDA_REQUEST_MEM_UNMAP:
> +		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_MEM_UNMAP, data,
> +					  file_priv->driver_priv);
> +	default:
> +		return -EINVAL;
> +	}
> +}

[Severity: High]
Can calling qda_fastrpc_invoke() from qda_ioctl_munmap() race with
qda_fastrpc_flush_pending() during rpmsg teardown and cause a refcount
underflow and use-after-free on struct qda_fastrpc_invoke_ctx?

In qda_fastrpc_invoke(), qda_fastrpc_context_alloc() initializes
ctx->refcount to 1 and inserts ctx into qdev->ctx_xa before the message is
prepared or sent:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_alloc() {
	...
	kref_init(&ctx->refcount);
	...
	ret = xa_alloc(&qdev->ctx_xa, &id, ctx, XA_LIMIT(1, QDA_FASTRPC_CTXID_MAX),
		       GFP_KERNEL);
	...
}

The second reference on ctx->refcount for the response handler is only
taken later in qda_rpmsg_send_msg(), and is dropped immediately if
rpmsg_send() fails or asynchronously via ctx->put_work after qda_rpmsg_cb()
runs. However, ctx is only erased from qdev->ctx_xa inside
qda_fastrpc_context_free() when the final reference is dropped.

If qda_rpmsg_remove() runs concurrently and calls
qda_fastrpc_flush_pending():

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_flush_pending() {
	...
	xa_for_each(&qdev->ctx_xa, idx, ctx) {
		ctx->retval = -EPIPE;
		complete(&ctx->work);
		kref_put(&ctx->refcount, qda_fastrpc_context_free);
	}
}

while an invocation in qda_fastrpc_invoke() is still in
qda_fastrpc_prepare_args() or qda_fastrpc_invoke_pack(), or after
qda_rpmsg_send_msg() failed (for example with -ENODEV at drm_dev_enter()),
or after qda_rpmsg_cb() already queued ctx->put_work, won't
qda_fastrpc_flush_pending() drop a reference that was never held by the
pending response path, freeing ctx before the caller or worker drops its
own reference?

> diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
> index bd2d0f4308b10..43598ddef461d 100644
> --- a/include/uapi/drm/qda_accel.h
> +++ b/include/uapi/drm/qda_accel.h
[ ... ]
> @@ -173,6 +180,29 @@ struct drm_qda_mem_map {
>  	__u64 vaddrout;
>  };
>  
> +/**
> + * struct drm_qda_mem_unmap - Parameters for removing a DSP mapping
> + * @request: QDA_REQUEST_MUNMAP or QDA_REQUEST_MEM_UNMAP
> + * @dsp_handle: Identifier the DSP tracks the buffer by (QDA_REQUEST_MEM_UNMAP)
> + * @vaddr: Address the mapping was made at (QDA_REQUEST_MEM_UNMAP)
> + * @vaddrout: Address the mapping was made at (QDA_REQUEST_MUNMAP)
> + * @size: Length of the mapping in bytes
> + *
> + * Removes a mapping made with DRM_IOCTL_QDA_REMOTE_MMAP, named by the address
> + * that call returned in drm_qda_mem_map.vaddrout. The request must match the
> + * one the mapping was made with: QDA_REQUEST_MUNMAP uses @vaddrout and @size,
> + * QDA_REQUEST_MEM_UNMAP uses @dsp_handle, @vaddr and @size. The buffer is not
> + * affected, so its handle must stay open for as long as the DSP uses the
> + * mapping.
> + */
> +struct drm_qda_mem_unmap {
> +	__u32 request;
> +	__s32 dsp_handle;
> +	__u64 vaddr;
> +	__u64 vaddrout;
> +	__u64 size;
> +};

[Severity: Low]
As reported by Sashiko on v1 [1], struct drm_qda_mem_unmap defines two
separate address fields (vaddr and vaddrout) for the same DSP virtual
address returned in drm_qda_mem_map.vaddrout, and qda_ioctl_munmap() does
not verify that the fields unused by the selected request type (dsp_handle
and vaddr for QDA_REQUEST_MUNMAP, or vaddrout for QDA_REQUEST_MEM_UNMAP)
are zero. Could a single address field be used here, with unused fields
checked for zero in qda_ioctl_munmap()?

[1] https://lore.kernel.org/r/20260519-qda-series-v1-15-b2d984c297f8@oss.qualcomm.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=18

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

* Re: [PATCH v4 15/25] accel/qda: Add DSP process creation and release
  2026-11-01 22:21 ` [PATCH v4 15/25] accel/qda: Add DSP process creation and release Ekansh Gupta
@ 2026-11-01 22:50   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:50 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] accel/qda: DMA UAF of init_mem_gem_obj on signal in qda_fastrpc_invoke()
- [Critical] accel/qda: race in qda_session_set_init_mem() frees active process memory
- [Critical] accel/qda: unprivileged userspace can invoke QDA_FASTRPC_INIT_HANDLE
- [High] accel/qda: deadlock in qda_postclose() holding drm_dev_enter() across RPC
- [High] accel/qda: default session->pd of QDA_ROOT_PD allows unattached root PD invoke
--

commit 981b12f498982e2e454ee8c3d214b24f64472c31
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add DSP process creation and release

This commit adds FastRPC requests and the DRM_IOCTL_QDA_REMOTE_SESSION_CREATE
ioctl to create a DSP process on a session and release it when the session
closes. It allocates per-session memory for the DSP process image and tracks
the session's protection domain for subsequent requests.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 7d44342d3f67b..983c6a3dcfb45 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
[ ... ]
> @@ -101,7 +105,16 @@ EXPORT_SYMBOL_FOR_MODULES(qda_session_put, "qda_fastrpc_shim");
>  
>  static void qda_postclose(struct drm_device *dev, struct drm_file *file)
>  {
> -	qda_session_put(file->driver_priv);
> +	struct qda_session *session = file->driver_priv;
> +	int idx;
> +
> +	/* Only a session that created a process has one to release */
> +	if (READ_ONCE(session->init_mem_gem_obj) && drm_dev_enter(dev, &idx)) {
> +		qda_release_dsp_process(session->qda_dev, session);
> +		drm_dev_exit(idx);
> +	}

[Severity: High]
Can holding drm_dev_enter() across qda_release_dsp_process() in
qda_postclose() deadlock with qda_rpmsg_remove() if the DSP crashes or the
RPMsg transport disconnects while the release request is in flight?

qda_release_dsp_process() calls qda_fastrpc_invoke() ->
qda_fastrpc_invoke_common(), which sends the QDA_FASTRPC_RMID_INIT_RELEASE
message (inside qda_rpmsg_send_msg(), which already uses its own short
drm_dev_enter()/drm_dev_exit() section around rpmsg_send()) and then blocks in
qda_rpmsg_wait_for_rsp() via wait_for_completion_interruptible(&ctx->work).

If the RPMsg device is removed before the DSP replies, qda_rpmsg_remove() runs:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_remove() {
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_fastrpc_flush_pending(qdev);
	...
}

drm_dev_unplug() calls synchronize_srcu(&drm_unplug_srcu) and waits for
qda_postclose() to call drm_dev_exit(idx), while qda_postclose() is blocked in
qda_rpmsg_wait_for_rsp() waiting for qda_fastrpc_flush_pending() to complete
ctx->work (and when qda_postclose() runs during process exit after
exit_signals() or from the delayed_fput workqueue, signals cannot interrupt the
wait).

Should qda_postclose() check !drm_dev_is_unplugged(dev) instead of holding
drm_dev_enter() across the blocking RPC wait?

[ ... ]
> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 693dd6f035d2a..4672405ea4b79 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
[ ... ]
> @@ -511,9 +572,14 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
>  	/* Ensure the payload is visible to the DSP before the doorbell */
>  	dma_wmb();
>  
> -	msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
> +	/* The session has no process yet to identify while one is being created */
> +	if (ctx->handle == QDA_FASTRPC_INIT_HANDLE)
> +		msg->fastrpc.remote_session_id = 0;
> +	else
> +		msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
> +
>  	msg->fastrpc.tid = current->pid;
> -	msg->fastrpc.ctx = ctx->ctxid | QDA_USER_PD;
> +	msg->fastrpc.ctx = ctx->ctxid | ctx->pd;
>  	msg->fastrpc.handle = ctx->handle;
>  	msg->fastrpc.sc = ctx->sc;
>  	msg->fastrpc.addr = msg->phys;

[Severity: Critical]
What prevents unprivileged user space from invoking QDA_FASTRPC_INIT_HANDLE
directly via DRM_IOCTL_QDA_REMOTE_INVOKE or the legacy invoke path?

In qda_fastrpc_invoke_pack(), any invocation with ctx->handle ==
QDA_FASTRPC_INIT_HANDLE is formatted with msg->fastrpc.remote_session_id = 0.
However, neither qda_fastrpc_prepare_args_invoke() nor
qda_fastrpc_invoke_legacy() rejects user-supplied requests where handle ==
QDA_FASTRPC_INIT_HANDLE:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_prepare_args_invoke() {
	ctx->handle = invoke_args->handle;
	ctx->sc = invoke_args->sc;
	...
}

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_invoke_legacy() {
	...
	ctx->type = QDA_FASTRPC_RMID_INVOKE_DYNAMIC;
	ctx->legacy = true;
	ctx->pd = READ_ONCE(session->pd);
	ctx->handle = handle;
	ctx->sc = sc;
	...
}

Doesn't this allow an unprivileged caller to forge kernel initialisation
requests to QDA_FASTRPC_INIT_HANDLE with remote_session_id = 0—for example,
invoking QDA_FASTRPC_RMID_INIT_CREATE / QDA_FASTRPC_RMID_INIT_CREATE_ATTR for a
signed process without CAP_SYS_ADMIN, supplying arbitrary physical page
descriptors in argument 3, or sending QDA_FASTRPC_RMID_INIT_RELEASE with
another session's remote_session_id?

[ ... ]
> @@ -740,8 +913,23 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
>  	 */
>  	qda_session_get(session);
>  	ctx->session = session;
> -
> -	err = qda_fastrpc_prepare_args_invoke(ctx, argp);
> +	ctx->pd = READ_ONCE(session->pd);
> +
> +	switch (ctx->type) {
> +	case QDA_FASTRPC_RMID_INIT_RELEASE:
> +		err = qda_fastrpc_prepare_args_release_process(ctx);
> +		break;
> +	case QDA_FASTRPC_RMID_INIT_CREATE:
> +		/* The new process is in the user PD, and so is the request creating it */
> +		ctx->pd = QDA_USER_PD;
> +		err = qda_fastrpc_prepare_args_init_create(ctx, argp);
> +		break;
> +	case QDA_FASTRPC_RMID_INVOKE_DYNAMIC:
> +		err = qda_fastrpc_prepare_args_invoke(ctx, argp);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}

[Severity: High]
Because qda_session_alloc() zero-initializes struct qda_session, session->pd
starts as 0 (QDA_ROOT_PD).

If user space calls DRM_IOCTL_QDA_REMOTE_INVOKE (via qda_ioctl_invoke() ->
qda_fastrpc_prepare_args()) or FASTRPC_IOCTL_INVOKE (via
qda_fastrpc_invoke_legacy()) before creating a DSP process on the session,
ctx->pd is set to READ_ONCE(session->pd) == QDA_ROOT_PD, and
qda_fastrpc_invoke_pack() packs QDA_ROOT_PD into msg->fastrpc.ctx.

Can an unprivileged caller use this to direct invocations at QDA_ROOT_PD before
any protection domain has been created or attached?

[ ... ]
> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index e9f4151e344af..f51ad0bc9e9cc 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -75,7 +75,22 @@ 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(void *data, struct qda_session *session)
> +/*
> + * A process created again replaces the first, so the memory held for the
> + * previous one is dropped. Swapped atomically, as ioctls on one file can run
> + * concurrently.
> + */
> +static void qda_session_set_init_mem(struct qda_session *session, struct qda_gem_obj *mem)
> +{
> +	struct qda_gem_obj *old;
> +
> +	drm_gem_object_get(&mem->base);
> +	old = xchg(&session->init_mem_gem_obj, mem);
> +	if (old)
> +		drm_gem_object_put(&old->base);
> +}

[Severity: Critical]
As reported by Sashiko on v1 [1], is an atomic xchg() in
qda_session_set_init_mem() sufficient when two threads call
DRM_IOCTL_QDA_REMOTE_SESSION_CREATE concurrently on the same file?

In qda_fastrpc_invoke(), qda_fastrpc_invoke_common() and
qda_session_set_init_mem() run without a per-session lock serializing process
creation. If Thread A (with memA) and Thread B (with memB) issue
QDA_FASTRPC_RMID_INIT_CREATE concurrently:

1. The DSP processes Thread A first and Thread B second, so the active DSP
   process is replaced and runs in memB.
2. On the CPU, Thread B runs qda_session_set_init_mem(session, memB) before
   Thread A runs qda_session_set_init_mem(session, memA).
3. Thread A swaps session->init_mem_gem_obj to memA and calls
   drm_gem_object_put(&memB->base).
4. When Thread B drops ctx->refcount at out_put, qda_fastrpc_context_free()
   drops the last reference to memB, unmapping and freeing memB while the DSP
   process is actively running in it.

Should process creation on a session be serialized with a mutex (or rejected if
a process already exists)?

> +
> +static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
>  {
>  	struct qda_dev *qdev = session->qda_dev;
>  	struct qda_fastrpc_invoke_ctx *ctx;
> @@ -85,11 +100,26 @@ static int qda_fastrpc_invoke(void *data, struct qda_session *session)
>  	if (IS_ERR(ctx))
>  		return PTR_ERR(ctx);
>  
> +	ctx->type = type;
> +
>  	err = qda_fastrpc_prepare_args(ctx, session, data);
>  	if (err)
>  		goto out_put;
>  
>  	err = qda_fastrpc_invoke_common(qdev, ctx);
> +	if (err)
> +		goto out_put;
> +
> +	/*
> +	 * The session only changes once the DSP has accepted the request, so a
> +	 * failed create leaves the previous process, if any, as it was. The
> +	 * memory is freed with the session, which the invocation keeps alive
> +	 * until it is done, so a release needs no reference of its own.
> +	 */
> +	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
> +		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
> +		WRITE_ONCE(session->pd, QDA_USER_PD);
> +	}
>  
>  out_put:
>  	kref_put(&ctx->refcount, qda_fastrpc_context_free);

[Severity: Critical]
What happens if a thread calling DRM_IOCTL_QDA_REMOTE_SESSION_CREATE is
interrupted by a signal while qda_fastrpc_invoke_common() is waiting in
qda_rpmsg_wait_for_rsp()?

After qda_rpmsg_send_msg() has sent the QDA_FASTRPC_RMID_INIT_CREATE request to
the DSP, a signal causes wait_for_completion_interruptible(&ctx->work) to
return -ERESTARTSYS. Here in qda_fastrpc_invoke(), the error check jumps to
out_put without calling qda_session_set_init_mem(session,
ctx->init_mem_gem_obj).

The DSP still processes the in-flight create request, loads the ELF image into
ctx->init_mem_gem_obj, starts the process, and replies. When qda_rpmsg_cb()
receives the reply and runs qda_fastrpc_context_put_wq() ->
qda_fastrpc_context_free():

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_context_free() {
	...
	if (ctx->init_mem_gem_obj)
		drm_gem_object_put(&ctx->init_mem_gem_obj->base);
	...
}

Because qda_session_set_init_mem() never took a reference for the session,
qda_fastrpc_context_free() drops the only reference to ctx->init_mem_gem_obj
and frees the DMA buffer while the DSP process is running in it. In addition,
session->init_mem_gem_obj remains NULL, so qda_postclose() later skips
qda_release_dsp_process() and qda_session_release() releases
session->assigned_iommu_dev while the orphaned DSP process is still using that
context bank.

Could ctx->init_mem_gem_obj be retained by the session whenever an INIT_CREATE
request has been sent to the DSP (or transferred when the DSP replies with
status 0)?

[1] https://lore.kernel.org/r/20260519-qda-series-v1-13-b2d984c297f8@oss.qualcomm.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=15

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

* Re: [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP
  2026-11-01 22:21 ` [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP Ekansh Gupta
@ 2026-11-01 22:51   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:51 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] accel/qda: UAF and context bank reuse when qda_gem_obj outlives qda_session
- [High] accel/qda: missing vm_ops in qda_gem_object_funcs leaks GEM buffers on mmap
- [High] accel/qda: UAF on iommu_dev->dev in qda_dma_free() after qda_cb_unpopulate()
- [High] accel/qda: missing drm_dev_enter() in qda_ioctl_gem_create() races with unplug
- [Medium] accel/qda: 32-bit dma_addr_t in qda_gem_obj truncates stream ID bits
- [Medium] accel/qda: 32-bit size_t truncation of args->size in qda_ioctl_gem_create()
- [Medium] accel/qda: coherent_dma_mask left at 32 bits in qda_create_cb_device()
--

commit 28e778f1e5490a2de285e597396980427ca74098
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add buffer allocation for the DSP

This commit adds GEM buffer allocation and mmap offset ioctls for the QDA
DSP accelerator driver, backing buffers with coherent DMA allocations from
per-session IOMMU context banks and folding the context bank stream ID into
DSP-visible addresses.

> diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
> new file mode 100644
> index 0000000000000..864bac914e48d
> --- /dev/null
> +++ b/drivers/accel/qda/qda_gem.c
> @@ -0,0 +1,108 @@
[ ... ]
> +static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struct *vma)
> +{
> +	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
> +	int ret;
> +
> +	/* The fake offset only identifies the object, it does not index into it */
> +	vma->vm_pgoff = 0;
> +
> +	ret = qda_dma_mmap(qda_gem_obj, vma);
> +	if (ret)
> +		return ret;
> +
> +	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
> +
> +	return 0;
> +}
> +
> +static const struct drm_gem_object_funcs qda_gem_object_funcs = {
> +	.free = qda_gem_free_object,
> +	.mmap = qda_gem_mmap_obj,
> +};

[Severity: High]
Does qda_gem_object_funcs leak every mmap()ed qda_gem_obj and its coherent
DMA buffer?

When user space maps a buffer on the accel fd, the call chain is:

  drm_gem_mmap()
    drm_gem_mmap_obj()
      drm_gem_object_get(obj);
      vma->vm_ops = obj->funcs->vm_ops;
      obj->funcs->mmap(obj, vma); -> qda_gem_mmap_obj()

drm_gem_mmap_obj() takes a reference on the GEM object for the VMA and
relies on vma->vm_ops->close (normally drm_gem_vm_close) to drop that
reference when the VMA is unmapped, as well as vma->vm_ops->open
(drm_gem_vm_open) to take additional references when the VMA is forked or
split.

Because qda_gem_object_funcs leaves .vm_ops unset and neither
qda_gem_mmap_obj() nor qda_dma_mmap() initializes vma->vm_ops,
drm_gem_vm_close() is never called on munmap() or process exit, so the GEM
object reference count never drops to zero and qda_gem_free_object() never
runs.

Should qda_gem_object_funcs set .vm_ops to a vm_operations_struct wired to
drm_gem_vm_open and drm_gem_vm_close?

> diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
> new file mode 100644
> index 0000000000000..af3b9dfc75099
> --- /dev/null
> +++ b/drivers/accel/qda/qda_gem.h
> @@ -0,0 +1,44 @@
[ ... ]
> +struct qda_gem_obj {
> +	/** @base: DRM GEM object base; must be first member */
> +	struct drm_gem_object base;
> +	/** @iommu_dev: Context bank this buffer is mapped into */
> +	struct qda_iommu_device *iommu_dev;
> +	/** @virt: Kernel virtual address of the buffer */
> +	void *virt;
> +	/** @dma_addr: Address of the buffer as the DSP sees it */
> +	dma_addr_t dma_addr;

[Severity: Medium]
Can storing the DSP-visible address in dma_addr_t truncate the folded
stream ID bits when dma_addr_t is 32 bits wide?

In drivers/accel/qda/qda_soc.h, qda_soc_sid_offset() returns a u64 with the
stream ID shifted to soc_data->sid_pos (32 on default_soc_data and 56 on
kaanapali_soc_data), and qda_soc_dma_addr() takes a u64 iova.

However, struct qda_gem_obj declares dma_addr as dma_addr_t. On 32-bit
builds without CONFIG_ARCH_DMA_ADDR_T_64BIT (which are permitted since
DRM_ACCEL_QDA depends on ARCH_QCOM || COMPILE_TEST), assigning:

  gem_obj->dma_addr = dma_addr +
                      qda_soc_sid_offset(iommu_dev->qdev->soc_data,
                                         iommu_dev->sid);

in qda_dma_alloc() truncates the upper 32 bits and discards the stream ID.
Should struct qda_gem_obj::dma_addr be u64 instead of dma_addr_t?

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index 380c2526d9d2c..b11743ab791e7 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
[ ... ]
> +int qda_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file_priv)
> +{
> +	struct qda_dev *qdev = qda_dev_from_drm(dev);
> +	struct drm_qda_gem_create *args = data;
> +	struct drm_gem_object *gem_obj;
> +
> +	if (args->pad)
> +		return -EINVAL;
> +
> +	gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr, args->size,
> +					file_priv->driver_priv);

[Severity: High]
Can qda_ioctl_gem_create() race with qda_rpmsg_remove() because it does not
guard hardware/manager access with drm_dev_enter() and drm_dev_exit()?

In drivers/accel/qda/qda_rpmsg.c, qda_rpmsg_remove() runs:

  drm_dev_unplug(&qdev->drm_dev);
  qda_cb_unpopulate(qdev);
  qda_deinit_device(qdev);
    qda_memory_manager_exit(qdev->iommu_mgr);

drm_ioctl() only performs a point-in-time drm_dev_is_unplugged() check
before invoking the driver ioctl callback; drm_dev_unplug() only waits for
readers inside a drm_dev_enter() / drm_dev_exit() section.

If qda_rpmsg_remove() runs right after a thread enters
qda_ioctl_gem_create(), drm_dev_unplug() returns immediately and
qda_cb_unpopulate() and qda_memory_manager_exit() can destroy the context
bank devices and free qdev->iommu_mgr->devices concurrently with
qda_gem_create_object() -> qda_memory_manager_alloc() -> qda_dma_alloc().

[Severity: Medium]
Also in qda_ioctl_gem_create(), can passing args->size (__u64) to
qda_gem_create_object() (which takes size_t) silently truncate sizes >= 4 GiB
on 32-bit builds?

When args->size exceeds SIZE_MAX on a 32-bit kernel (for example
0x100001000ULL), the implicit conversion to size_t narrows the value to its
lower 32 bits before PAGE_ALIGN(size) is checked in qda_gem_create_object(),
allocating a smaller buffer than requested instead of returning -EINVAL.

> diff --git a/drivers/accel/qda/qda_memory_dma.c b/drivers/accel/qda/qda_memory_dma.c
> new file mode 100644
> index 0000000000000..0f4715a55b9de
> --- /dev/null
> +++ b/drivers/accel/qda/qda_memory_dma.c
> @@ -0,0 +1,72 @@
[ ... ]
> +int qda_dma_alloc(struct qda_iommu_device *iommu_dev,
> +		  struct qda_gem_obj *gem_obj, size_t size)
> +{
> +	dma_addr_t dma_addr;
> +	void *virt;
> +
> +	virt = dma_alloc_coherent(iommu_dev->dev, size, &dma_addr, GFP_KERNEL);
> +	if (!virt)
> +		return -ENOMEM;

[Severity: Medium]
Are coherent allocations in qda_dma_alloc() restricted to a 32-bit DMA mask
even on 34-bit CDSP context banks such as kaanapali_soc_data?

In drivers/accel/qda/qda_cb.c, qda_create_cb_device() configures the
context bank platform device with:

  dma_bits = qda_soc_dma_addr_bits(qdev);
  ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(dma_bits));

Because of_platform_device_create() initializes pdev->dev.dma_mask to
&pdev->platform_dma_mask and pdev->dev.coherent_dma_mask to
DMA_BIT_MASK(32), calling dma_set_mask() only updates
*pdev->dev.dma_mask and leaves pdev->dev.coherent_dma_mask at 32 bits.

When qda_dma_alloc() calls dma_alloc_coherent(), the IOMMU DMA allocator
bounds the IOVA using iommu_dev->dev->coherent_dma_mask rather than
*iommu_dev->dev->dma_mask, so 34-bit CDSP context banks on Kaanapali
(where dma_addr_bits_cdsp is 34) remain limited to 32-bit IOVAs. Should
qda_create_cb_device() call dma_set_mask_and_coherent() instead?

> +	gem_obj->virt = virt;
> +	gem_obj->dma_addr = dma_addr +
> +			    qda_soc_sid_offset(iommu_dev->qdev->soc_data, iommu_dev->sid);
> +	gem_obj->iommu_dev = iommu_dev;
> +
> +	return 0;
> +}
> +
> +/**
> + * qda_dma_free() - Release a buffer back to its context bank
> + * @gem_obj: GEM object to free
> + */
> +void qda_dma_free(struct qda_gem_obj *gem_obj)
> +{
> +	dma_free_coherent(gem_obj->iommu_dev->dev, gem_obj->size,
> +			  gem_obj->virt, qda_gem_dma_addr(gem_obj));

[Severity: Critical]
What happens if a qda_gem_obj outlives the drm_file session that created it?

A GEM object can stay alive after its drm_file is closed, for example if
user space exports it to a dma-buf fd via DRM_IOCTL_PRIME_HANDLE_TO_FD
(enabled by DRIVER_GEM) or keeps a VMA mapped after closing the accel fd.

When the drm_file closes, the teardown path runs:

  qda_postclose()
    qda_session_free()
      qda_memory_manager_release_device(session->assigned_iommu_dev)
        iommu_dev->assigned_session = NULL;
        kref_put(&iommu_dev->ref, iommu_device_free);

Because qda_dma_alloc() stores gem_obj->iommu_dev without holding a
reference on iommu_dev or keeping the context bank reserved while buffers
are still allocated from it:

1. Setting iommu_dev->assigned_session = NULL allows
   qda_memory_manager_assign_device() to hand the same context bank to a new
   session while the previous session's DMA buffer is still mapped into that
   context bank's IOMMU domain.

2. If the context bank is also unregistered via qda_cb_teardown_device() ->
   qda_memory_manager_unregister_device(), iommu_dev->ref drops to zero and
   iommu_device_free() frees iommu_dev, causing a use-after-free in
   qda_dma_free() (and qda_dma_mmap()) when it later dereferences
   gem_obj->iommu_dev->dev and gem_obj->iommu_dev->qdev->soc_data.

[Severity: High]
Similarly, in qda_dma_free(), can gem_obj->iommu_dev->dev be a dangling
pointer if the RPMsg device is removed while a user process still holds an
open session or GEM buffer?

In drivers/accel/qda/qda_cb.c, qda_cb_unpopulate() runs during
qda_rpmsg_remove():

  qda_cb_teardown_device(&entry->pdev->dev);
  of_platform_device_destroy(&entry->pdev->dev, NULL);

While struct qda_iommu_device itself is refcounted via iommu_dev->ref so
that it can survive qda_cb_teardown_device(), of_platform_device_destroy()
immediately unregisters and frees entry->pdev (which is iommu_dev->dev).
When the user process later closes its fd or frees the GEM handle,
qda_gem_free_object() -> qda_memory_manager_free() -> qda_dma_free() passes
the freed gem_obj->iommu_dev->dev to dma_free_coherent().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=6

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

* Re: [PATCH v4 07/25] accel/qda: Add DMA-BUF import
  2026-11-01 22:21 ` [PATCH v4 07/25] accel/qda: Add DMA-BUF import Ekansh Gupta
@ 2026-11-01 22:51   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:51 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] accel/qda: unreferenced iommu_dev in qda_gem_obj causes UAF and CB reuse
- [High] accel/qda: UAF on CB device unplug in qda_gem_free_object() and import
- [High] accel/qda: missing vm_ops in qda_gem_object_funcs leaks GEM ref on mmap
- [Medium] accel/qda: 32-bit dma_addr_t truncates SID in qda_gem_prime_import_sg_table()
- [Low] accel/qda: unused gem_prime_import_sg_table hook and stale import comments
- [Low] accel/qda: qda_gem_prime_import_sg_table() allocates unused shmem file
--

commit c21f00e936e2fb1a3bd269969e95dd625e477ff7
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add DMA-BUF import

This commit adds DMA-BUF PRIME import and sg_table export support to the
QDA accelerator driver, mapping imported buffers into the importing
session's IOMMU context bank and verifying that mappings are physically
or IOVA contiguous.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 37019ff38ecbc..5e953d1f98bf4 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
[ ... ]
> @@ -69,6 +70,9 @@ static const struct drm_driver qda_drm_driver = {
>  	.postclose = qda_postclose,
>  	.ioctls = qda_ioctls,
>  	.num_ioctls = ARRAY_SIZE(qda_ioctls),
> +	.gem_prime_import = qda_gem_prime_import,
> +	.gem_prime_import_sg_table = qda_gem_prime_import_sg_table,
> +	.prime_fd_to_handle = qda_prime_fd_to_handle,
>  	.name = QDA_DRIVER_NAME,
>  	.desc = "Qualcomm DSP Accelerator Driver",
>  };

[Severity: Low]
This isn't a bug, but is .gem_prime_import_sg_table in qda_drm_driver ever
called by the DRM core?

In DRM PRIME, dev->driver->gem_prime_import_sg_table is only invoked by
drm_gem_prime_import_dev(). However, qda_gem_prime_import() does not call
drm_gem_prime_import_dev(); it open-codes dma_buf_attach() and
dma_buf_map_attachment_unlocked() and calls qda_gem_prime_import_sg_table()
directly.

The commit message ("The core PRIME helper takes the device to attach to, so
pass the session's context bank") and the kernel-doc comment on
qda_gem_prime_import_sg_table() ("Called by the DRM PRIME helpers once they
have attached to the exporter...") also describe the
drm_gem_prime_import_dev() flow rather than the open-coded path in
qda_gem_prime_import(). Could qda_gem_prime_import() either call
drm_gem_prime_import_dev(drm_dev, dma_buf, session->assigned_iommu_dev->dev)
after handling the same-device check, or drop the unused hook and update the
comments?

> diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
> index 864bac914e48d..a9ab8ccb78930 100644
> --- a/drivers/accel/qda/qda_gem.c
> +++ b/drivers/accel/qda/qda_gem.c
[ ... ]
> @@ -24,6 +31,10 @@ static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struc
>  	struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
>  	int ret;
>  
> +	/* Imported buffers have to be mapped through the exporter */
> +	if (drm_gem_is_imported(gem_obj))
> +		return -EINVAL;
> +
>  	/* The fake offset only identifies the object, it does not index into it */
>  	vma->vm_pgoff = 0;
>  
> @@ -36,8 +47,35 @@ static int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struc
>  	return 0;
>  }
>  
> -static const struct drm_gem_object_funcs qda_gem_object_funcs = {
> +/*
> + * Lets another context bank map this buffer when a session imports the
> + * dma-buf this object was exported as: see qda_gem_prime_import().
> + */
> +static struct sg_table *qda_gem_get_sg_table(struct drm_gem_object *gem_obj)
> +{
[ ... ]
> +	return sgt;
> +}
> +
> +const struct drm_gem_object_funcs qda_gem_object_funcs = {
>  	.free = qda_gem_free_object,
> +	.get_sg_table = qda_gem_get_sg_table,
>  	.mmap = qda_gem_mmap_obj,
>  };

[Severity: High]
Does mmap() on a QDA GEM object or a PRIME-exported QDA DMA-BUF leak the
drm_gem_object reference and its coherent DMA buffer?

Both drm_gem_mmap_obj() (for DRM fd mmap) and
drm_gem_dmabuf_mmap()->drm_gem_prime_mmap() (for PRIME-exported DMA-BUF mmap,
now reachable because .get_sg_table is wired up) take a reference via
drm_gem_object_get(obj), set vma->vm_ops = obj->funcs->vm_ops, and call
obj->funcs->mmap(obj, vma).

Because qda_gem_object_funcs leaves .vm_ops unset and
qda_gem_mmap_obj()->qda_dma_mmap()->dma_mmap_coherent() does not set
vma->vm_ops either, vma->vm_ops remains NULL (and is replaced with
dummy_vm_ops in mmap_region()). When the VMA is later unmapped,
drm_gem_vm_close() is never called to drop the reference acquired at mmap
time, so qda_gem_free_object() never runs and qda_gem_obj and its DMA memory
are leaked.

Additionally, in the PRIME DMA-BUF mmap path, drm_gem_prime_mmap() adds
drm_vma_node_start(&obj->vma_node) to vma->vm_pgoff before calling
obj->funcs->mmap(). Resetting vma->vm_pgoff = 0 in qda_gem_mmap_obj() instead
of subtracting drm_vma_node_start(&gem_obj->vma_node) discards any non-zero
page offset passed to mmap() on the exported DMA-BUF fd.

Should qda_gem_object_funcs set .vm_ops to a vm_operations_struct with
.open = drm_gem_vm_open and .close = drm_gem_vm_close, and adjust
vma->vm_pgoff -= drm_vma_node_start(&gem_obj->vma_node) in qda_gem_mmap_obj()?

> diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
> index af3b9dfc75099..1dab494197aab 100644
> --- a/drivers/accel/qda/qda_gem.h
> +++ b/drivers/accel/qda/qda_gem.h
[ ... ]
> @@ -16,14 +17,20 @@ struct qda_session;
>  
>  /**
>   * struct qda_gem_obj - A buffer the DSP can access
> + *
> + * Either allocated by this driver or imported from another one. Use
> + * drm_gem_is_imported() on @base to tell which; imported buffers have no
> + * @virt and own no memory, they only hold a mapping of someone else's.
>   */
>  struct qda_gem_obj {
>  	/** @base: DRM GEM object base; must be first member */
>  	struct drm_gem_object base;
>  	/** @iommu_dev: Context bank this buffer is mapped into */
>  	struct qda_iommu_device *iommu_dev;
> -	/** @virt: Kernel virtual address of the buffer */
> +	/** @virt: Kernel virtual address, NULL for imported buffers */
>  	void *virt;
> +	/** @sgt: Mapping of an imported buffer, NULL otherwise */
> +	struct sg_table *sgt;
>  	/** @dma_addr: Address of the buffer as the DSP sees it */
>  	dma_addr_t dma_addr;

[Severity: Medium]
Can qda_gem_obj->dma_addr truncate the stream ID offset on 32-bit
CONFIG_COMPILE_TEST builds where dma_addr_t is 32 bits wide?

In qda_gem_prime_import_sg_table():

	qda_gem_obj->dma_addr = sg_dma_address(sgt->sgl) +
				qda_soc_sid_offset(qdev->soc_data, iommu_dev->sid);

qda_soc_sid_offset() returns a u64 with sid shifted left by 32 or 56 bits,
and qda_soc_dma_addr() takes a u64 iova, but struct qda_gem_obj stores
dma_addr as dma_addr_t. When !CONFIG_ARCH_DMA_ADDR_T_64BIT, storing the sum
into dma_addr_t drops the upper 32 bits containing the SID. Should dma_addr in
struct qda_gem_obj be u64?

> diff --git a/drivers/accel/qda/qda_prime.c b/drivers/accel/qda/qda_prime.c
> new file mode 100644
> index 0000000000000..7a709e3000d8b
> --- /dev/null
> +++ b/drivers/accel/qda/qda_prime.c
> @@ -0,0 +1,184 @@
[ ... ]
> +struct drm_gem_object *qda_gem_prime_import_sg_table(struct drm_device *drm_dev,
> +						     struct dma_buf_attachment *attach,
> +						     struct sg_table *sgt)
> +{
> +	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
> +	size_t size = PAGE_ALIGN(attach->dmabuf->size);
> +	struct qda_iommu_device *iommu_dev;
> +	struct qda_gem_obj *qda_gem_obj;
> +	int ret;
> +
> +	if (drm_prime_get_contiguous_size(sgt) < size) {
> +		drm_dbg_driver(drm_dev, "Rejecting non-contiguous buffer of %zu bytes\n", size);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	/*
> +	 * The buffer was mapped into the context bank of the session doing the
> +	 * import, so that is the bank the DSP has to be told about.
> +	 */
> +	iommu_dev = dev_get_drvdata(attach->dev);
> +	if (!iommu_dev)
> +		return ERR_PTR(-ENODEV);
> +
> +	qda_gem_obj = kzalloc_obj(*qda_gem_obj);
> +	if (!qda_gem_obj)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ret = drm_gem_object_init(drm_dev, &qda_gem_obj->base, size);
> +	if (ret) {
> +		kfree(qda_gem_obj);
> +		return ERR_PTR(ret);
> +	}

[Severity: Low]
Does an imported DMA-BUF GEM object need shmem backing via
drm_gem_object_init()?

drm_gem_object_init() calls drm_gem_private_object_init() and then allocates a
shmem file of size bytes via shmem_file_setup(). Since imported DMA-BUF
objects are backed by the exporter's memory rather than shmem (and also if
shmem_file_setup() fails, kfree(qda_gem_obj) is called without
drm_gem_private_object_fini() to clean up _resv and gpuva.lock), should
qda_gem_prime_import_sg_table() call drm_gem_private_object_init() instead?

> +
> +	qda_gem_obj->base.funcs = &qda_gem_object_funcs;
> +	qda_gem_obj->size = size;
> +	qda_gem_obj->sgt = sgt;
> +	qda_gem_obj->iommu_dev = iommu_dev;
> +	qda_gem_obj->dma_addr = sg_dma_address(sgt->sgl) +
> +				qda_soc_sid_offset(qdev->soc_data, iommu_dev->sid);
> +
> +	return &qda_gem_obj->base;
> +}
> +
[ ... ]
> +struct drm_gem_object *qda_gem_prime_import(struct drm_device *drm_dev,
> +					    struct dma_buf *dma_buf)
> +{
> +	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
> +	struct qda_session *session = qdev->importing_session;
> +	struct dma_buf_attachment *attach;
> +	struct drm_gem_object *obj;
> +	struct sg_table *sgt;
> +	int ret;
> +
> +	if (!session || !session->assigned_iommu_dev)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (drm_gem_is_prime_exported_dma_buf(drm_dev, dma_buf)) {
> +		/*
> +		 * A buffer this driver exported. The DRM core would hand back
> +		 * the original object, which stays in the context bank of the
> +		 * session that allocated it, while the DSP of this session
> +		 * translates addresses in its own bank. Share the object only
> +		 * within a bank, and map it again for any other.
> +		 */
> +		obj = dma_buf->priv;
> +		if (to_qda_gem_obj(obj)->iommu_dev == session->assigned_iommu_dev) {
> +			drm_gem_object_get(obj);
> +			return obj;
> +		}
> +	}

[Severity: High]
What happens if a session exports a QDA GEM object as a DMA-BUF (now enabled
via qda_gem_get_sg_table()) and then closes its DRM fd while the exported
dma_buf fd remains open?

When the exporting session closes its DRM fd:

  qda_postclose()
    -> qda_session_free()
      -> qda_memory_manager_release_device(session->assigned_iommu_dev)

qda_memory_manager_release_device() clears iommu_dev->assigned_session = NULL
and drops the session's kref on iommu_dev, even though the exported
qda_gem_obj is still alive and holds an unreferenced pointer in
qda_gem_obj->iommu_dev (and remains mapped in that context bank's SMMU
domain).

This appears to allow two failure modes:

1. If a second session opens the device, qda_memory_manager_assign_device()
can assign that same iommu_dev to the new session while the first session's
exported buffer is still mapped in that context bank. If the second session
then imports the dma_buf, to_qda_gem_obj(obj)->iommu_dev ==
session->assigned_iommu_dev in qda_gem_prime_import() evaluates to true and
returns the original non-imported GEM object (where drm_gem_is_imported(obj)
is false), allowing the second session to mmap() the buffer through QDA as well
as sharing the SMMU context bank before the old buffer is freed.

2. If the RPMsg device is removed after the exporting session closed its DRM
fd while the exported dma_buf is still held open:

  qda_rpmsg_remove()
    -> qda_cb_unpopulate()
      -> qda_cb_teardown_device()
        -> qda_memory_manager_unregister_device()

drops the remaining kref on iommu_dev and frees iommu_dev via kfree(). Any
subsequent import/attach calling qda_gem_get_sg_table()->qda_dma_get_sgtable()
or final release calling
qda_gem_free_object()->qda_memory_manager_free()->qda_dma_free() will then
dereference the freed qda_gem_obj->iommu_dev pointer.

Should each qda_gem_obj hold a reference on iommu_dev (and keep the context
bank from being reassigned until all GEM allocations/mappings in it have been
released)?

> +
> +	attach = dma_buf_attach(dma_buf, session->assigned_iommu_dev->dev);
> +	if (IS_ERR(attach))
> +		return ERR_CAST(attach);
[ ... ]
> +int qda_prime_fd_to_handle(struct drm_device *drm_dev, struct drm_file *file_priv,
> +			   int prime_fd, u32 *handle)
> +{
> +	struct qda_dev *qdev = qda_dev_from_drm(drm_dev);
> +	struct qda_session *session = file_priv->driver_priv;
> +	int ret;
> +
> +	ret = qda_memory_manager_assign_device(qdev->iommu_mgr, session);
> +	if (ret)
> +		return ret;
> +
> +	guard(mutex)(&qdev->import_lock);
> +
> +	qdev->importing_session = session;
> +	ret = drm_gem_prime_fd_to_handle(drm_dev, file_priv, prime_fd, handle);
> +	qdev->importing_session = NULL;
> +
> +	return ret;
> +}

[Severity: High]
Can qda_prime_fd_to_handle() and qda_gem_free_object() race with or access
freed context bank devices after qda_rpmsg_remove()?

In qda_rpmsg_remove():

  drm_dev_unplug(&qdev->drm_dev);
  qda_cb_unpopulate(qdev);
  qda_deinit_device(qdev);

First, drm_ioctl_kernel() only checks drm_dev_is_unplugged() at entry and does
not hold a drm_dev_enter()/drm_dev_exit() critical section. Because
qda_prime_fd_to_handle() does not guard its body with
drm_dev_enter()/drm_dev_exit(), drm_dev_unplug() will not wait for an
in-flight import to complete before qda_cb_unpopulate() destroys the context
bank platform devices and qda_deinit_device() frees qdev->iommu_mgr->devices
and destroys qdev->import_lock.

Second, in qda_gem_prime_import(), dma_buf_attach(dma_buf,
session->assigned_iommu_dev->dev) attaches the DMA-BUF to the context bank
child platform_device (&entry->pdev->dev) without holding a reference that
keeps the device registered. If qda_rpmsg_remove() runs while a user process
has an open session with imported (or allocated) GEM objects,
qda_cb_unpopulate() calls of_platform_device_destroy(&entry->pdev->dev, NULL),
which unregisters and frees entry->pdev. When the user process later closes
its fd, qda_gem_free_object() calls:

  drm_prime_gem_destroy(gem_obj, qda_gem_obj->sgt)
    -> dma_buf_unmap_attachment_unlocked()
      -> dma_unmap_sgtable(attach->dev, ...)

(or qda_dma_free() -> dma_free_coherent(gem_obj->iommu_dev->dev, ...)),
dereferencing the unregistered and freed context bank struct device.

Should qda_prime_fd_to_handle() use drm_dev_enter()/drm_dev_exit(), and should
the context bank device lifetime be tied to active GEM objects/attachments or
managed via drmm so it outlives open file descriptors and GEM objects?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=7

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

* Re: [PATCH v4 03/25] accel/qda: Add compute context bank devices
  2026-11-01 22:21 ` [PATCH v4 03/25] accel/qda: Add compute context bank devices Ekansh Gupta
@ 2026-11-01 22:52   ` sashiko-bot
  2026-11-02  8:43   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:52 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] accel/qda: missing IOMMU probe and domain ownership in qda_create_cb_device()
- [Medium] accel/qda: qda_create_cb_device() leaves coherent_dma_mask at 32 bits
--

commit 3c742ed6e8932c7c7fe69686db2d02383880697a
Author: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>

accel/qda: Add compute context bank devices

This commit creates a platform device for each qcom,fastrpc-compute-cb
child node during rpmsg probe and configures its DMA address mask based on
per-SoC data.

> diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
> new file mode 100644
> index 0000000000000..afb0bb71a1f44
> --- /dev/null
> +++ b/drivers/accel/qda/qda_cb.c
> @@ -0,0 +1,76 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> +#include <linux/dma-mapping.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +#include <drm/drm_print.h>
> +#include "qda_cb.h"
> +#include "qda_drv.h"
> +#include "qda_soc.h"
> +
> +static int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
> +{
> +	struct qda_cb_dev *entry;
> +	struct platform_device *pdev;
> +	u32 dma_bits;
> +	int ret;
> +
> +	pdev = of_platform_device_create(cb_node, NULL, qdev->dev);
> +	if (!pdev) {
> +		drm_err(&qdev->drm_dev, "Failed to create CB device for %pOFn\n", cb_node);
> +		return -ENODEV;
> +	}

[Severity: High]
Can qda_create_cb_device() succeed here even when the context bank device has
not been attached to an IOMMU domain or claimed for kernel DMA API use?

Because the iommus property lives on the qcom,fastrpc-compute-cb child nodes
rather than the parent fastrpc rpmsg node, fw_devlink only creates a
DL_FLAG_SYNC_STATE_ONLY proxy link on rpdev->dev, which
device_links_check_suppliers() ignores when qda_rpmsg_probe() runs.

When of_platform_device_create() calls of_device_add() -> device_add(),
bus_notify(dev, BUS_NOTIFY_ADD_DEVICE) ignores the return value of
iommu_bus_notifier(), and iommu_init_device() ignores the return value of
dev->bus->dma_configure(dev):

drivers/iommu/iommu.c:iommu_init_device() {
	if (!dev->iommu->fwspec && !dev->driver && dev->bus->dma_configure) {
		mutex_unlock(&iommu_probe_device_lock);
		dev->bus->dma_configure(dev);
		mutex_lock(&iommu_probe_device_lock);
		/* If another instance finished the job for us, skip it */
		if (!dev->iommu || dev->iommu_group)
			return -ENODEV;
	}
}

If the SMMU supplier has not probed yet, of_iommu_configure() returns
-EPROBE_DEFER, causing of_dma_configure_id() to return early before
arch_setup_dma_ops() sets dev->dma_coherent:

drivers/of/device.c:of_dma_configure_id() {
	ret = of_iommu_configure(dev, np, id);
	if (ret == -EPROBE_DEFER) {
		/* Don't touch range map if it wasn't set from a valid dma-ranges */
		if (set_map)
			dev->dma_range_map = NULL;
		kfree(map);
		return -EPROBE_DEFER;
	}
}

In the platform bus model, DMA and IOMMU configuration errors (such as
-EPROBE_DEFER or SMMU stream mapping failures) are only propagated when a
driver binds in really_probe() -> platform_dma_configure(). Since no platform
driver binds to pdev, of_platform_device_create() still returns a valid pdev
with pdev->dev.iommu_group == NULL, allowing the DRM device to be registered
while subsequent session DMA allocations fall back to unisolated dma-direct
physical mappings.

Additionally, when platform_dma_configure() is invoked from
iommu_init_device() with drv == NULL, it returns before calling
iommu_device_use_default_domain():

drivers/base/platform.c:platform_dma_configure() {
	/* @dev->driver may not be valid when we're called from the IOMMU layer */
	if (ret || !drv || to_platform_driver(drv)->driver_managed_dma)
		return ret;

	ret = iommu_device_use_default_domain(dev);
}

This leaves group->owner_cnt at 0, so kernel DMA API ownership of the context
bank's IOMMU group is never claimed (unlike drivers/misc/fastrpc.c, which
binds a platform driver to qcom,fastrpc-compute-cb devices). Should a
component/sub-driver bind to these context bank platform devices, or should
qda_create_cb_device() explicitly configure DMA and claim default domain
ownership while propagating errors such as -EPROBE_DEFER?

> +
> +	dma_bits = qda_soc_dma_addr_bits(qdev);
> +	ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(dma_bits));

[Severity: Medium]
Should qda_create_cb_device() call dma_set_mask_and_coherent() instead of
dma_set_mask() here?

When of_platform_device_create() allocates the device through
of_platform_device_create_pdata() -> of_device_alloc() ->
platform_device_alloc() -> setup_pdev_dma_masks(), pdev->dev.dma_mask is
pointed at pdev->platform_dma_mask while pdev->dev.coherent_dma_mask is
initialized separately to DMA_BIT_MASK(32):

drivers/base/platform.c:setup_pdev_dma_masks() {
	if (!pdev->dev.coherent_dma_mask)
		pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
	if (!pdev->dev.dma_mask) {
		pdev->platform_dma_mask = DMA_BIT_MASK(32);
		pdev->dev.dma_mask = &pdev->platform_dma_mask;
	}
}

Because dev->dev.dma_mask is already non-NULL when
of_platform_device_create_pdata() checks if (!dev->dev.dma_mask),
dev->dev.dma_mask and dev->dev.coherent_dma_mask remain separate fields.

Calling dma_set_mask() only updates *pdev->dev.dma_mask
(pdev->platform_dma_mask) to 34 bits on Kaanapali CDSP and leaves
pdev->dev.coherent_dma_mask at DMA_BIT_MASK(32). When later patches allocate
context bank buffers via dma_alloc_coherent() (in commit "accel/qda: Add
buffer allocation for the DSP"), the IOMMU DMA allocator passes
dev->coherent_dma_mask as the upper IOVA limit to iommu_dma_alloc_iova(),
restricting coherent allocations on 34-bit CDSP context banks to 32-bit
IOVAs.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=3

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

* Re: [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space
  2026-11-01 22:21 ` [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space Ekansh Gupta
@ 2026-11-01 22:54   ` sashiko-bot
  2026-11-02  9:24   ` Konrad Dybcio
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:54 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] accel/qda: premature GEM object put in qda_fastrpc_context_free() on mmap
- [High] accel/qda: missing offset bounds check in setup_mmap_pages() for mem_map
- [High] accel/qda: uninitialized session PD and leaked mappings in qda_ioctl_mmap()
- [Medium] accel/qda: drm_qda_mem_map.attrs is ignored and unused fields are unvalidated
- [Medium] accel/qda: DSP mapping leak on signal interruption in qda_fastrpc_invoke()
--

commit 395c018ba64267dfbd2bc4c753e6cc750574fd40
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add remote memory mapping to the DSP address space

This commit adds the DRM_IOCTL_QDA_REMOTE_MMAP ioctl to map a buffer handle
into the remote processor's address space via QDA_FASTRPC_RMID_INIT_MMAP or
QDA_FASTRPC_RMID_INIT_MEM_MAP and return the mapped address to user space.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 35ce60f3de2ec..744d7317cbe11 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c
> @@ -3,6 +3,7 @@
>  #include <linux/bitfield.h>
>  #include <linux/capability.h>
>  #include <linux/dma-mapping.h>
> +#include <linux/limits.h>
>  #include <linux/mm.h>
>  #include <linux/sched.h>
>  #include <linux/slab.h>
> @@ -251,6 +252,9 @@ void qda_fastrpc_context_free(struct kref *ref)
>  	if (ctx->init_mem_gem_obj)
>  		drm_gem_object_put(&ctx->init_mem_gem_obj->base);
>  
> +	if (ctx->map_obj)
> +		drm_gem_object_put(ctx->map_obj);

[Severity: Critical]
Could dropping ctx->map_obj here when the invocation context is freed cause a
use-after-free while the mapping remains active on the remote processor?

In setup_mmap_pages(), get_gem_obj_from_handle() stores the buffer reference
in ctx->map_obj and passes qda_gem_obj->dma_addr to the remote processor for
QDA_FASTRPC_RMID_INIT_MMAP or QDA_FASTRPC_RMID_INIT_MEM_MAP.

However, as soon as the mapping call finishes in qda_fastrpc_invoke():

    qda_ioctl_mmap()
      -> qda_fastrpc_invoke()
           -> kref_put(&ctx->refcount, qda_fastrpc_context_free)
                -> drm_gem_object_put(ctx->map_obj)

the kernel drops its reference to ctx->map_obj before qda_ioctl_mmap()
returns to user space, unlike ctx->init_mem_gem_obj which is transferred to
the session via qda_session_set_init_mem().

If user space then closes the buffer handle with DRM_IOCTL_GEM_CLOSE while
the remote mapping at vaddrout is still in place, qda_gem_free_object() will
free the backing memory and release its address in session->assigned_iommu_dev.
Because driver-allocated buffers (such as alloc_kernel_gem() buffers and
ctx->msg_gem_obj) allocate from the same session->assigned_iommu_dev context
bank, couldn't later ioctls reuse that address and allow remote code to
access or corrupt in-flight control messages? Also, should margs->flags be
validated in qda_fastrpc_prepare_args_map() before forwarding it to
QDA_FASTRPC_RMID_INIT_MMAP?

[ ... ]

> @@ -890,6 +930,148 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
>  	return err;
>  }
>  
> +/*
> + * Find the buffer a mapping request names and describe it to the DSP. The
> + * buffer is held by the context until the request is done, so that closing its
> + * handle meanwhile cannot free memory the DSP is being told to map. The
> + * region must fit in the buffer, as the DSP would otherwise map past its end.
> + */
> +static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct drm_qda_mem_map *margs,
> +			    struct qda_phy_page *pages)
> +{
> +	struct qda_gem_obj *qda_gem_obj;
> +	int err;
> +
> +	if (!margs->handle)
> +		return -EINVAL;
> +
> +	err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
> +	if (err)
> +		return err;
> +
> +	qda_gem_obj = to_qda_gem_obj(ctx->map_obj);
> +	if (margs->size > qda_gem_obj->size)
> +		return -EINVAL;
> +
> +	pages->addr = qda_gem_obj->dma_addr;
> +	pages->size = margs->size ? margs->size : qda_gem_obj->size;

[Severity: High]
Does setup_mmap_pages() need to check margs->offset against qda_gem_obj->size
as well?

In qda_fastrpc_prepare_args_mem_map(), margs->offset is only checked against
INT_MAX before being written to req->offset:

	if (margs->offset > INT_MAX)
		return -EINVAL;
	...
	req->offset = margs->offset;

And setup_mmap_pages() only verifies that margs->size <= qda_gem_obj->size,
unlike process_msg_buffer() which checks both the offset and the remaining
length:

	if (ctx->args[i].offset > qda_gem_obj->size ||
	    len > qda_gem_obj->size - ctx->args[i].offset) {

When DRM_IOCTL_QDA_REMOTE_MMAP is called with QDA_REQUEST_MEM_MAP and a
non-zero margs->offset (for example with margs->size == 0, which sets
pages->size to qda_gem_obj->size, or with margs->offset + pages->size
exceeding qda_gem_obj->size), won't this allow mapping past the end of
qda_gem_obj?

> +
> +	return 0;
> +}

[ ... ]

> +static int qda_fastrpc_prepare_args_mem_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
> +{
> +	struct drm_qda_mem_map *margs = argp;
> +	struct qda_fastrpc_mem_map_req_msg *req;
> +	struct qda_gem_obj *kernel_gem;
> +	u8 *gv;
> +	int err;
> +
> +	const size_t off_req = 0;
> +	const size_t off_pages = off_req + sizeof(*req);
> +	const size_t off_rsp = off_pages + sizeof(struct qda_phy_page);
> +	const size_t total_size = off_rsp + sizeof(struct qda_fastrpc_map_rsp_msg);
> +
> +	/* The wire field is signed */
> +	if (margs->offset > INT_MAX)
> +		return -EINVAL;
> +
> +	ctx->sc = QDA_FASTRPC_SCALARS(QDA_FASTRPC_RMID_INIT_MEM_MAP, 3, 1);
> +	ctx->handle = QDA_FASTRPC_INIT_HANDLE;
> +
> +	err = alloc_kernel_args(ctx, 4);
> +	if (err)
> +		return err;
> +
> +	kernel_gem = alloc_kernel_gem(ctx, total_size);
> +	if (IS_ERR(kernel_gem))
> +		return PTR_ERR(kernel_gem);
> +	gv = kernel_gem->virt;
> +
> +	req = (struct qda_fastrpc_mem_map_req_msg *)(gv + off_req);
> +	req->remote_session_id = ctx->session->remote_session_id;
> +	req->handle = margs->dsp_handle;
> +	req->offset = margs->offset;
> +	req->flags = margs->flags;
> +	req->vaddrin = margs->vaddrin;
> +	req->num = sizeof(struct qda_phy_page);
> +	req->data_len = 0;

[Severity: Medium]
Should margs->attrs be handled or rejected here, and should unused fields in
struct drm_qda_mem_map be checked for zero in qda_fastrpc_prepare_args_map()?

The comment on struct drm_qda_mem_map in include/uapi/drm/qda_accel.h states:

 * Makes a buffer visible to the DSP of this file ahead of the calls that use
 * it. QDA_REQUEST_MMAP maps a whole buffer and uses @handle, @flags,
 * @vaddrin, @size and @vaddrout. QDA_REQUEST_MEM_MAP maps a region of it with
 * caller-chosen attributes and uses every field.

However, qda_fastrpc_prepare_args_mem_map() never reads or validates
margs->attrs, and qda_fastrpc_prepare_args_map() does not reject non-zero
values in margs->attrs, margs->offset, or margs->dsp_handle when request is
QDA_REQUEST_MMAP.

[ ... ]

> @@ -953,6 +1135,12 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx,
>  		ctx->pd = QDA_USER_PD;
>  		err = qda_fastrpc_prepare_args_init_create(ctx, argp);
>  		break;
> +	case QDA_FASTRPC_RMID_INIT_MMAP:
> +		err = qda_fastrpc_prepare_args_map(ctx, argp);
> +		break;
> +	case QDA_FASTRPC_RMID_INIT_MEM_MAP:
> +		err = qda_fastrpc_prepare_args_mem_map(ctx, argp);
> +		break;

[Severity: High]
What happens if user space calls DRM_IOCTL_QDA_REMOTE_MMAP before creating a
session process, or closes the file while mappings are still active?

When a file is opened in qda_open(), qda_session_alloc() zero-initializes
struct qda_session, leaving session->pd as 0 (QDA_ROOT_PD) and
session->init_mem_gem_obj as NULL until DRM_IOCTL_QDA_REMOTE_SESSION_CREATE
completes. In qda_fastrpc_prepare_args(), ctx->pd is initialized from
READ_ONCE(session->pd), so calling qda_ioctl_mmap() on a freshly opened file
will send QDA_FASTRPC_RMID_INIT_MMAP or QDA_FASTRPC_RMID_INIT_MEM_MAP to
QDA_ROOT_PD without an initialized session or a capability check.

In addition, when the DRM file is closed, qda_postclose() only calls
qda_release_dsp_process() when session->init_mem_gem_obj is non-NULL:

	if (READ_ONCE(session->init_mem_gem_obj) && drm_dev_enter(dev, &idx)) {
		qda_release_dsp_process(session->qda_dev, session);
		drm_dev_exit(idx);
	}

and nothing tracks or unmaps active DRM_IOCTL_QDA_REMOTE_MMAP mappings before
drm_gem_release() frees the GEM objects and qda_session_release() releases
session->assigned_iommu_dev. Doesn't this leak the remote mappings on close?

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index 99b8a2517726b..cd3cea72f50e8 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -110,6 +110,8 @@ static int qda_fastrpc_invoke(u32 type, void *data, struct qda_session *session)
>  	if (err)
>  		goto out_put;
>  
> +	qda_fastrpc_return_result(ctx, data);

[Severity: Medium]
Can a signal interrupting qda_fastrpc_invoke_common() leak the remote mapping
in the DSP address space?

After qda_rpmsg_send_msg() sends a QDA_FASTRPC_RMID_INIT_MMAP or
QDA_FASTRPC_RMID_INIT_MEM_MAP request, qda_rpmsg_wait_for_rsp() waits in
wait_for_completion_interruptible(&ctx->work).

If a signal arrives before the reply, qda_fastrpc_invoke_common() returns
-ERESTARTSYS and qda_fastrpc_invoke() jumps to out_put, skipping
qda_fastrpc_return_result(). When the DSP later finishes the mapping and
writes rsp->vaddrout into ctx->gem_objs[idx], qda_rpmsg_cb() drops the last
reference on ctx and frees the buffer without returning vaddrout to user
space or unmapping the region on the DSP.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=17

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

* Re: [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim
  2026-11-01 22:21 ` [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim Ekansh Gupta
@ 2026-11-01 22:57   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 22:57 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] accel/qda: use-after-free race on placeholder in qda_shim_mem_map()
- [High] accel/qda: unmap size mismatch in qda_shim_mmap() when size is 0
- [High] accel/qda: missing unplug check in shim_mappings_release() on close
- [High] accel/qda: premature buffer release when legacy unmap fails or is interrupted
- [Medium] accel/qda: race in shim_mapping_detach() and shim_mapping_attach()
- [Medium] accel/qda: missing offset bounds check in setup_mmap_pages()
--

commit b1739b465f1ac98dac259c9a90724960699924f1
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add remote memory map and unmap to the legacy shim

This commit implements the four legacy FastRPC memory map and unmap ioctls
in the QDA compatibility shim by importing DMA-BUF file descriptors and
tracking live mappings per open file so buffers remain referenced while
mapped on the DSP.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index dbd14cd07cb20..b35604fce630c 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c

[ ... ]

> @@ -935,19 +935,33 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
>   * buffer is held by the context until the request is done, so that closing its
>   * handle meanwhile cannot free memory the DSP is being told to map. The
>   * region must fit in the buffer, as the DSP would otherwise map past its end.
> + *
> + * A caller with no handle table supplies the buffer already imported. It is
> + * checked as a buffer named by handle would be: an fd can name a buffer that
> + * another session exported, which resolves to the original object, still
> + * mapped in that session's context bank.
>   */
> -static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct drm_qda_mem_map *margs,
> +static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, struct qda_mem_map_args *args,
>  			    struct qda_phy_page *pages)
>  {
> +	struct drm_qda_mem_map *margs = &args->map;
>  	struct qda_gem_obj *qda_gem_obj;
>  	int err;
>  
> -	if (!margs->handle)
> -		return -EINVAL;
> +	if (args->obj) {
> +		if (to_qda_gem_obj(args->obj)->iommu_dev != ctx->session->assigned_iommu_dev)
> +			return -EACCES;
>  
> -	err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
> -	if (err)
> -		return err;
> +		drm_gem_object_get(args->obj);
> +		ctx->map_obj = args->obj;
> +	} else {
> +		if (!margs->handle)
> +			return -EINVAL;
> +
> +		err = get_gem_obj_from_handle(ctx, margs->handle, &ctx->map_obj);
> +		if (err)
> +			return err;
> +	}
>  
>  	qda_gem_obj = to_qda_gem_obj(ctx->map_obj);
>  	if (margs->size > qda_gem_obj->size)
>  		return -EINVAL;
>  
>  	pages->addr = qda_gem_obj->dma_addr;
>  	pages->size = margs->size ? margs->size : qda_gem_obj->size;

[Severity: Medium]
Can a caller of FASTRPC_IOCTL_MEM_MAP or DRM_IOCTL_QDA_REMOTE_MMAP (with
QDA_REQUEST_MEM_MAP) map past the end of qda_gem_obj by supplying a
non-zero margs->offset?

In qda_shim_mem_map(), only args.offset < 0 is rejected, and
qda_fastrpc_prepare_args_mem_map() only checks margs->offset > INT_MAX
before setting req->offset = margs->offset.

Here in setup_mmap_pages(), only margs->size > qda_gem_obj->size is
checked. If a caller passes 0 < margs->offset <= INT_MAX and
margs->size <= qda_gem_obj->size (including margs->size == 0, which sets
pages->size to qda_gem_obj->size) such that margs->offset + pages->size
exceeds qda_gem_obj->size, shouldn't setup_mmap_pages() also check that
margs->offset <= qda_gem_obj->size and pages->size <= qda_gem_obj->size -
margs->offset?

> diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
> index 4a2ba20ee01ac..c46d2de4f5818 100644
> --- a/drivers/accel/qda/qda_fastrpc_shim.c
> +++ b/drivers/accel/qda/qda_fastrpc_shim.c

[ ... ]

> @@ -79,6 +101,83 @@ static struct drm_gem_object *shim_import_fd(struct qda_session *session, s32 fd
>  	return obj;
>  }
>  
> +/* Takes ownership of @obj on success; on failure the caller still owns it */
> +static struct qda_shim_mapping *shim_mapping_add(struct qda_shim_file *sf,
> +						 struct list_head *list, u64 key, u64 vaddr,
> +						 u64 size, struct drm_gem_object *obj)
> +{
> +	struct qda_shim_mapping *m, *new;
> +
> +	new = kzalloc_obj(*new);
> +	if (!new)
> +		return ERR_PTR(-ENOMEM);
> +
> +	new->key = key;
> +	new->vaddr = vaddr;
> +	new->size = size;
> +	new->obj = obj;
> +
> +	guard(mutex)(&sf->maps_lock);
> +
> +	list_for_each_entry(m, list, node) {
> +		if (m->key == key) {
> +			kfree(new);
> +			return ERR_PTR(-EEXIST);
> +		}
> +	}
> +	list_add_tail(&new->node, list);
> +
> +	return new;
> +}
> +
> +static struct qda_shim_mapping *shim_mapping_detach(struct qda_shim_file *sf,
> +						    struct list_head *list, u64 key, u64 vaddr)
> +{
> +	struct qda_shim_mapping *m;
> +
> +	guard(mutex)(&sf->maps_lock);
> +
> +	list_for_each_entry(m, list, node) {
> +		if (m->key == key && m->vaddr == vaddr) {
> +			list_del(&m->node);
> +			return m;
> +		}
> +	}
> +
> +	return NULL;
> +}
> +
> +static void shim_mapping_attach(struct qda_shim_file *sf, struct list_head *list,
> +				struct qda_shim_mapping *m)
> +{
> +	guard(mutex)(&sf->maps_lock);
> +	list_add_tail(&m->node, list);
> +}

[Severity: Medium]
Can detaching a mapping in shim_mapping_detach() and dropping sf->maps_lock
before validating size or calling the blocking unmap RPC race with
concurrent map and unmap calls on the same open file?

First, in qda_shim_mem_unmap(), m is removed from sf->mem_map_list while
qda_legacy_ops_table.mem_unmap() runs without sf->maps_lock held. While m
is detached, its key (args.fd) is absent from sf->mem_map_list, so a
concurrent qda_shim_mem_map() with the same fd can pass the duplicate-key
check in shim_mapping_add() and insert a second entry with the same fd. If
the in-flight mem_unmap() then fails (for example, interrupted by a signal
with -ERESTARTSYS in wait_for_completion_interruptible()),
shim_mapping_attach() unconditionally appends m back onto sf->mem_map_list
with list_add_tail(), leaving duplicate fd keys in sf->mem_map_list.

Second, in both qda_shim_mmap() and qda_shim_mem_map(), m is visible on the
list before copy_to_user() runs. If a concurrent qda_shim_munmap() (for
example, with a mismatched args.size since m->size != args.size is checked
after shim_mapping_detach()) or a failing qda_shim_mem_unmap() temporarily
detaches m while copy_to_user() faults, the error rollback call to
shim_mapping_detach() in the map path will return NULL and skip unmapping
and freeing m, after which the failing unmap path calls
shim_mapping_attach() and re-attaches m even though the map ioctl returned
-EFAULT.

> +
> +static void shim_mapping_free(struct qda_shim_mapping *m)
> +{
> +	drm_gem_object_put(m->obj);
> +	kfree(m);
> +}
> +
> +/* The DSP unmap comes first: the buffer must outlive the DSP's use of it */
> +static void shim_mappings_release(struct qda_shim_file *sf)
> +{
> +	struct qda_shim_mapping *m, *tmp;
> +
> +	list_for_each_entry_safe(m, tmp, &sf->mmap_list, node) {
> +		list_del(&m->node);
> +		qda_legacy_ops_table.munmap(sf->session, m->vaddr, m->size);
> +		shim_mapping_free(m);
> +	}
> +
> +	list_for_each_entry_safe(m, tmp, &sf->mem_map_list, node) {
> +		list_del(&m->node);
> +		qda_legacy_ops_table.mem_unmap(sf->session, m->key, m->vaddr, m->size);
> +		shim_mapping_free(m);
> +	}
> +}

[Severity: High]
What happens in qda_shim_release() -> shim_mappings_release() if the file
is closed after the underlying rpmsg device has been removed?

When the rpmsg device is removed while a /dev/fastrpc-* file is still open,
qda_rpmsg_remove() calls drm_dev_unplug(), qda_cb_unpopulate() (destroying
each context-bank platform device), and qda_deinit_device() (destroying
qdev->iommu_mgr->assignment_lock and qdev->ctx_xa), and devres frees
qdev->iommu_mgr.

In drivers/accel/qda/qda_legacy_ops.c, legacy_release() checks
drm_dev_is_unplugged(&session->qda_dev->drm_dev) before calling
qda_release_dsp_process(). However, qda_shim_release() calls
shim_mappings_release() before legacy_release(), and neither
shim_mappings_release() nor legacy_munmap() and legacy_mem_unmap() checks
drm_dev_is_unplugged(). Won't closing a file with active mappings after
unplug call into qda_fastrpc_invoke() -> xa_alloc(&qdev->ctx_xa, ...) and
alloc_kernel_gem() -> qda_memory_manager_alloc(), dereferencing the
destroyed ctx_xa, devm-freed qdev->iommu_mgr, and destroyed context-bank
platform device?

[ ... ]

> @@ -191,6 +295,156 @@ static long qda_shim_init_create(struct qda_shim_file *sf, void __user *argp)
>  	return err;
>  }
>  
> +static long qda_shim_mmap(struct qda_shim_file *sf, void __user *argp)
> +{
> +	struct qda_shim_mapping *m;
> +	struct fastrpc_req_mmap args;
> +	struct drm_gem_object *obj;
> +	int err;
> +
> +	if (copy_from_user(&args, argp, sizeof(args)))
> +		return -EFAULT;
> +
> +	obj = shim_import_fd(sf->session, args.fd);
> +	if (IS_ERR(obj))
> +		return PTR_ERR(obj);
> +	if (!obj)
> +		return -EINVAL;
> +
> +	err = qda_legacy_ops_table.mmap(sf->session, obj, args.vaddrin, args.size, args.flags,
> +					&args.vaddrout);
> +	if (err) {
> +		drm_gem_object_put(obj);
> +		return err;
> +	}
> +
> +	m = shim_mapping_add(sf, &sf->mmap_list, args.vaddrout, args.vaddrout, args.size, obj);

[Severity: High]
If user space passes args.size == 0 to FASTRPC_IOCTL_MMAP (or
args.length == 0 to FASTRPC_IOCTL_MEM_MAP in qda_shim_mem_map()), doesn't
this record m->size = 0 while the DSP actually mapped the full GEM object
size?

In setup_mmap_pages():

	pages->addr = qda_gem_obj->dma_addr;
	pages->size = margs->size ? margs->size : qda_gem_obj->size;

When args.size is 0, the DSP maps qda_gem_obj->size bytes, but
qda_shim_mmap() and qda_shim_mem_map() record 0 in m->size. When the
mapping is later unmapped via qda_shim_munmap(), qda_shim_mem_unmap(),
copy_to_user() failure rollback, or shim_mappings_release(), the DSP is
asked to unmap 0 bytes before shim_mapping_free() drops the reference on
obj, and FASTRPC_IOCTL_MUNMAP calls specifying the actual buffer size will
be rejected by m->size != args.size. Should m->size record the effective
mapped size (or should a zero size/length be rejected)?

> +	if (IS_ERR(m)) {
> +		err = PTR_ERR(m);
> +		goto err_unmap;
> +	}
> +
> +	if (copy_to_user(argp, &args, sizeof(args))) {
> +		m = shim_mapping_detach(sf, &sf->mmap_list, args.vaddrout, args.vaddrout);
> +		if (m) {
> +			qda_legacy_ops_table.munmap(sf->session, m->vaddr, m->size);
> +			shim_mapping_free(m);
> +		}
> +		return -EFAULT;
> +	}
> +
> +	return 0;
> +
> +err_unmap:
> +	qda_legacy_ops_table.munmap(sf->session, args.vaddrout, args.size);
> +	drm_gem_object_put(obj);
> +
> +	return err;
> +}

[Severity: High]
Can ignoring the return value of munmap() and mem_unmap() in these rollback
paths (and in shim_mappings_release()) release m->obj while the DSP still
has the buffer mapped?

Here in qda_shim_mmap() (on copy_to_user() failure and at err_unmap), in
qda_shim_mem_map() (on copy_to_user() failure), and in
shim_mappings_release() on file close, qda_legacy_ops_table.munmap() or
mem_unmap() waits in qda_rpmsg_wait_for_rsp() via
wait_for_completion_interruptible(&ctx->work).

If a signal is pending or arrives while waiting (returning -ERESTARTSYS),
or if allocating the unmap invocation context fails, the DSP has not
unmapped the buffer, yet shim_mapping_free(m) or drm_gem_object_put(obj)
immediately drops the reference on the GEM object (which for an imported
DMA-BUF in the shim is the sole reference, tearing down its IOMMU mapping
or freeing its DMA memory in qda_gem_free_object()).

[ ... ]

> +static long qda_shim_mem_map(struct qda_shim_file *sf, void __user *argp)
> +{
> +	struct qda_shim_mapping *m;
> +	struct fastrpc_mem_map args;
> +	struct drm_gem_object *obj;
> +	int err;
> +
> +	if (copy_from_user(&args, argp, sizeof(args)))
> +		return -EFAULT;
> +
> +	if (args.offset < 0)
> +		return -EINVAL;
> +
> +	obj = shim_import_fd(sf->session, args.fd);
> +	if (IS_ERR(obj))
> +		return PTR_ERR(obj);
> +	if (!obj)
> +		return -EINVAL;
> +
> +	/* Reserved before the DSP is asked, so the same fd cannot be mapped twice */
> +	m = shim_mapping_add(sf, &sf->mem_map_list, args.fd, 0, args.length, obj);
> +	if (IS_ERR(m)) {
> +		drm_gem_object_put(obj);
> +		return PTR_ERR(m);
> +	}
> +
> +	err = qda_legacy_ops_table.mem_map(sf->session, obj, args.flags, args.attrs, args.offset,
> +					   args.vaddrin, args.length, args.fd, &args.vaddrout);
> +	if (err) {
> +		m = shim_mapping_detach(sf, &sf->mem_map_list, args.fd, 0);
> +		if (m)
> +			shim_mapping_free(m);
> +		return err;
> +	}
> +
> +	scoped_guard(mutex, &sf->maps_lock)
> +		m->vaddr = args.vaddrout;

[Severity: Critical]
Can a concurrent FASTRPC_IOCTL_MEM_UNMAP with args.vaddr == 0 detach and
free the placeholder m while qda_legacy_ops_table.mem_map() is still
running?

In qda_shim_mem_map(), shim_mapping_add() inserts m into sf->mem_map_list
with m->key = args.fd and m->vaddr = 0, and transfers the caller's only
reference on obj into m->obj before dropping sf->maps_lock:

	m = shim_mapping_add(sf, &sf->mem_map_list, args.fd, 0, args.length, obj);
	...
	err = qda_legacy_ops_table.mem_map(sf->session, obj, ...);

While qda_legacy_ops_table.mem_map() is executing without sf->maps_lock, a
concurrent qda_shim_mem_unmap() on the same file with the same args.fd and
args.vaddr == 0 will match the placeholder in shim_mapping_detach():

	list_for_each_entry(m, list, node) {
		if (m->key == key && m->vaddr == vaddr) {
			list_del(&m->node);
			return m;
		}
	}

If qda_legacy_ops_table.mem_unmap() then succeeds, qda_shim_mem_unmap()
calls shim_mapping_free(m), which puts m->obj (potentially before
setup_mmap_pages() has called drm_gem_object_get(args->obj)) and frees m.
When qda_legacy_ops_table.mem_map() finishes and returns 0,
qda_shim_mem_map() writes m->vaddr = args.vaddrout to the freed m and
leaves the DSP mapping active with m->obj already released.

Conversely, if qda_legacy_ops_table.mem_map() fails while the concurrent
qda_shim_mem_unmap() has detached m, qda_shim_mem_map()'s cleanup
shim_mapping_detach() returns NULL, and if mem_unmap() then fails and calls
shim_mapping_attach(), the failed placeholder with vaddr == 0 is
re-attached to sf->mem_map_list, leaking m->obj until file close and
blocking future FASTRPC_IOCTL_MEM_MAP calls on that fd with -EEXIST.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=19

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

* Re: [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim
  2026-11-01 22:21 ` [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim Ekansh Gupta
@ 2026-11-01 23:00   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 23:00 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] accel/qda: race between qda_session_attach() and qda_session_init_create()
- [High] accel/qda: use-after-free in qda_session_attach() during rpmsg removal
- [Medium] accel/qda: interrupted wait in qda_attach_audio_pd() resends remote heap
--

commit ae4f1060dc81ff84d55ddf1c5fd82d79333077f8
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add remote session attach to the legacy shim

This commit wires FASTRPC_IOCTL_INIT_ATTACH, FASTRPC_IOCTL_INIT_ATTACH_SNS,
and FASTRPC_IOCTL_INIT_CREATE_STATIC into the legacy FastRPC compatibility
shim by extracting qda_session_attach() and exposing it via
qda_legacy_ops_table.

> diff --git a/drivers/accel/qda/qda_fastrpc_shim.c b/drivers/accel/qda/qda_fastrpc_shim.c
> index c46d2de4f5818..c1464b1adbc2c 100644
> --- a/drivers/accel/qda/qda_fastrpc_shim.c
> +++ b/drivers/accel/qda/qda_fastrpc_shim.c
[ ... ]
> @@ -295,6 +296,23 @@ static long qda_shim_init_create(struct qda_shim_file *sf, void __user *argp)
>  	return err;
>  }
>  
> +static long qda_shim_attach(struct qda_shim_file *sf, u32 pd_type)
> +{
> +	return qda_legacy_ops_table.attach(sf->session, pd_type, NULL, 0);
> +}
> +
> +/* memlen is ignored: the Audio PD heap size comes from the device tree */
> +static long qda_shim_init_create_static(struct qda_shim_file *sf, void __user *argp)
> +{
> +	struct fastrpc_init_create_static args;
> +
> +	if (copy_from_user(&args, argp, sizeof(args)))
> +		return -EFAULT;
> +
> +	return qda_legacy_ops_table.attach(sf->session, QDA_ATTACH_AUDIO_PD,
> +					   u64_to_user_ptr(args.name), args.namelen);
> +}

[Severity: High]
Can qda_shim_attach() and qda_shim_init_create_static() (as well as
qda_ioctl_attach()) trigger a use-after-free if the underlying rpmsg device
is removed while an attach ioctl is in flight?

Both qda_shim_ioctl() and drm_ioctl() only perform a point-in-time
drm_dev_is_unplugged() check on entry without entering a drm_dev_enter()
critical section:

drivers/accel/qda/qda_fastrpc_shim.c:qda_shim_ioctl() {
	if (drm_dev_is_unplugged(&sf->session->qda_dev->drm_dev))
		return -ENODEV;

	return qda_shim_dispatch(sf, cmd, (void __user *)arg);
}

If qda_rpmsg_remove() runs after that check passes (for example while
qda_shim_init_create_static() or qda_attach_audio_pd() sleeps in
copy_from_user() or memdup_user()), drm_dev_unplug() does not wait for the
in-flight qda_session_attach() call before tearing down the device:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_remove() {
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_fastrpc_flush_pending(qdev);
	qda_cb_unpopulate(qdev);
	qda_deinit_device(qdev);
	qda_deinit_remote_heap(qdev);
}

qda_deinit_device() destroys qdev->iommu_mgr->assignment_lock and
qdev->ctx_xa, and devres teardown on &rpdev->dev frees qdev->iommu_mgr
(allocated with devm_kzalloc(qdev->dev, ...) in qda_init_device()). When the
in-flight qda_session_attach() proceeds into:

  qda_fastrpc_invoke()
    -> qda_fastrpc_context_alloc()
    -> qda_fastrpc_prepare_args()
      -> alloc_kernel_gem()
        -> qda_gem_create_object()
          -> qda_memory_manager_alloc()
            -> qda_memory_manager_assign_device()

wouldn't it access the destroyed qdev->ctx_xa and dereference the freed
qdev->iommu_mgr (or have its newly allocated ctx freed early if
qda_fastrpc_flush_pending() runs between qda_fastrpc_context_alloc() and
qda_rpmsg_send_msg())?

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index 41234fc576a9a..aaf3e5e6b5b18 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c
> @@ -183,7 +183,7 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
>   * so that a concurrent one cannot skip the heap while the first is still in
>   * flight, and the flag is set only once the DSP has accepted it.
>   */
> -static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remote_attach *args)
> +static int qda_attach_audio_pd(struct qda_session *session, const char __user *uname, u32 namelen)
>  {
>  	struct qda_dev *qdev = session->qda_dev;
>  	struct qda_static_pd_args sargs = {};
> @@ -193,20 +193,20 @@ static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remot
>  	if (strcmp(qdev->dsp_name, "adsp") || !qdev->remote_heap_size)
>  		return -ENODEV;
>  
> -	if (!args->namelen || args->namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
> +	if (!namelen || namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
>  		return -EINVAL;
>  
> -	name = memdup_user(u64_to_user_ptr(args->name), args->namelen);
> +	name = memdup_user(uname, namelen);
>  	if (IS_ERR(name))
>  		return PTR_ERR(name);
>  
> -	if (name[args->namelen - 1]) {
> +	if (name[namelen - 1]) {
>  		err = -EINVAL;
>  		goto out_free;
>  	}
>  
>  	sargs.name = name;
> -	sargs.namelen = args->namelen;
> +	sargs.namelen = namelen;
>  
>  	scoped_guard(mutex, &qdev->remote_heap_lock) {
>  		if (!qdev->audio_init_mem) {

[Severity: Medium]
What happens in qda_attach_audio_pd() if qda_fastrpc_invoke() is interrupted
by a signal after sending the request to the DSP?

Inside the qdev->remote_heap_lock guard:

	scoped_guard(mutex, &qdev->remote_heap_lock) {
		if (!qdev->audio_init_mem) {
			sargs.heap.addr = qdev->remote_heap_addr;
			sargs.heap.size = qdev->remote_heap_size;
		}

		err = qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE_STATIC, &sargs, session);
		if (!err && sargs.heap.size)
			qdev->audio_init_mem = true;
	}

qda_fastrpc_invoke_common() calls qda_rpmsg_send_msg() and then blocks in
qda_rpmsg_wait_for_rsp() via wait_for_completion_interruptible(&ctx->work).
If a signal interrupts the wait before the DSP replies, qda_fastrpc_invoke()
returns -ERESTARTSYS while the first QDA_FASTRPC_RMID_INIT_CREATE_STATIC
request is still in flight on the DSP.

Because err is non-zero, qdev->audio_init_mem remains false when
qdev->remote_heap_lock is released. Could a concurrent or restarted
FASTRPC_IOCTL_INIT_CREATE_STATIC / DRM_IOCTL_QDA_REMOTE_SESSION_ATTACH call
then observe !qdev->audio_init_mem and hand the remote heap to the DSP a
second time?

> @@ -226,38 +226,60 @@ static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remot
>  }
>  
>  /**
> - * qda_ioctl_attach() - Attach to a DSP-resident protection domain
> - * @dev: DRM device structure
> - * @data: User-space data (struct drm_qda_remote_attach)
> - * @file_priv: DRM file private data
> + * qda_session_attach() - Attach a session to a DSP-resident protection domain
> + * @session: Session to attach
> + * @pd_type: QDA_ATTACH_ROOT_PD, QDA_ATTACH_SENSORS_PD or QDA_ATTACH_AUDIO_PD
> + * @name: User pointer to the static PD name (QDA_ATTACH_AUDIO_PD only)
> + * @namelen: Length of @name including the NUL (QDA_ATTACH_AUDIO_PD only)
> + *
> + * Shared by the accel UAPI and the legacy shim, so the privilege check lives
> + * here rather than on the ioctl.
>   *
>   * Return: 0 on success, negative error code on failure
>   */
> -int qda_ioctl_attach(struct drm_device *dev, void *data, struct drm_file *file_priv)
> +int qda_session_attach(struct qda_session *session, u32 pd_type, const char __user *name,
> +		       u32 namelen)
>  {
> -	struct drm_qda_remote_attach *args = data;
> -	struct qda_session *session = file_priv->driver_priv;
>  	int pd;
>  
> +	if (!capable(CAP_SYS_ADMIN))
> +		return -EPERM;
> +
>  	if (READ_ONCE(session->init_mem_gem_obj))
>  		return -EBUSY;
>  
> -	switch (args->pd_type) {
> +	switch (pd_type) {
>  	case QDA_ATTACH_ROOT_PD:
>  	case QDA_ATTACH_SENSORS_PD:
> -		if (args->name || args->namelen)
> +		if (name || namelen)
>  			return -EINVAL;
>  
> -		pd = args->pd_type == QDA_ATTACH_ROOT_PD ? QDA_ROOT_PD : QDA_SENSORS_PD;
> +		pd = pd_type == QDA_ATTACH_ROOT_PD ? QDA_ROOT_PD : QDA_SENSORS_PD;
>  
>  		return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_ATTACH, &pd, session);
>  	case QDA_ATTACH_AUDIO_PD:
> -		return qda_attach_audio_pd(session, args);
> +		return qda_attach_audio_pd(session, name, namelen);
>  	default:
>  		return -EINVAL;
>  	}
>  }

[Severity: High]
Does qda_session_attach() race with concurrent qda_session_init_create()
calls on the same session?

Both the accel UAPI and the legacy shim allow concurrent ioctls on the same
open file without holding a per-session lock. In qda_session_attach(), the
check:

	if (READ_ONCE(session->init_mem_gem_obj))
		return -EBUSY;

runs unlocked before qda_fastrpc_invoke() sleeps waiting for the DSP
response, and qda_session_init_create() does not check whether the session
has already attached or is attaching. State updates only happen after the
DSP responds in qda_fastrpc_invoke():

	if (type == QDA_FASTRPC_RMID_INIT_CREATE) {
		qda_session_set_init_mem(session, ctx->init_mem_gem_obj);
		WRITE_ONCE(session->pd, QDA_USER_PD);
	} else if (type == QDA_FASTRPC_RMID_INIT_ATTACH ||
		   type == QDA_FASTRPC_RMID_INIT_CREATE_STATIC) {
		WRITE_ONCE(session->pd, ctx->pd);
	}

If one thread calls qda_session_attach() and another concurrently calls
qda_session_init_create() on the same file descriptor, both can pass
validation and send RPCs to the DSP. If QDA_FASTRPC_RMID_INIT_CREATE finishes
first (setting session->init_mem_gem_obj and session->pd = QDA_USER_PD) and
QDA_FASTRPC_RMID_INIT_ATTACH finishes second, session->pd is overwritten with
QDA_ROOT_PD or QDA_SENSORS_PD while session->init_mem_gem_obj remains
non-NULL.

When the file is later closed, legacy_release() or qda_postclose() sees
session->init_mem_gem_obj is non-NULL and calls qda_release_dsp_process(),
which sends QDA_FASTRPC_RMID_INIT_RELEASE directed at READ_ONCE(session->pd)
(now the attached PD instead of QDA_USER_PD). Wouldn't that leave the User PD
process running on the DSP while qda_session_release() frees
session->init_mem_gem_obj underneath it?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=22

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

* Re: [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap
  2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
@ 2026-11-01 23:00   ` sashiko-bot
  2026-11-02  8:43   ` Arnd Bergmann
  2026-11-02  8:50   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 23:00 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] accel/qda: race between qda_attach_audio_pd() and qda_rpmsg_remove()
- [High] accel/qda: unallocated CMA memory hyp-assigned in qda_init_remote_heap()
- [Medium] accel/qda: signal in qda_attach_audio_pd() desyncs audio_init_mem state
- [Medium] accel/qda: 32-bit BIT() overflow and unvalidated VMIDs in remote heap setup
- [Low] accel/qda: qda_attach_audio_pd() mixes goto cleanup with scoped_guard()
- [Low] accel/qda: missing mutex_destroy() for remote_heap_lock on device teardown
--

commit cd6c4ef6e7fc95f52a56e49a2e18e2d259cf294c
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add Audio protection domain attach and its remote heap

This commit adds support for attaching to the static Audio protection domain
by name via QDA_ATTACH_AUDIO_PD and hands the DSP its reserved remote heap
memory region on the first attach. At probe time, it parses the qcom,vmids
property and reserved-memory region and uses qcom_scm_assign_mem() to share
the heap with the DSP VMs until the rpmsg device is removed.

> diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
> index 9b8cccfea1e3a..41234fc576a9a 100644
> --- a/drivers/accel/qda/qda_ioctl.c
> +++ b/drivers/accel/qda/qda_ioctl.c

[ ... ]

> @@ -176,6 +177,54 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct qda_session *session)
>  	return qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_RELEASE, NULL, session);
>  }
>  
> +/*
> + * The Audio PD keeps the remote heap across daemon restarts, so only the first
> + * attach on a device hands it to the DSP. The lock is held across the attach
> + * so that a concurrent one cannot skip the heap while the first is still in
> + * flight, and the flag is set only once the DSP has accepted it.
> + */
> +static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remote_attach *args)
> +{
> +	struct qda_dev *qdev = session->qda_dev;
> +	struct qda_static_pd_args sargs = {};
> +	char *name;
> +	int err;
> +
> +	if (strcmp(qdev->dsp_name, "adsp") || !qdev->remote_heap_size)
> +		return -ENODEV;
> +
> +	if (!args->namelen || args->namelen > QDA_FASTRPC_INIT_NAMELEN_MAX)
> +		return -EINVAL;
> +
> +	name = memdup_user(u64_to_user_ptr(args->name), args->namelen);
> +	if (IS_ERR(name))
> +		return PTR_ERR(name);
> +
> +	if (name[args->namelen - 1]) {
> +		err = -EINVAL;
> +		goto out_free;
> +	}
> +
> +	sargs.name = name;
> +	sargs.namelen = args->namelen;
> +
> +	scoped_guard(mutex, &qdev->remote_heap_lock) {
> +		if (!qdev->audio_init_mem) {
> +			sargs.heap.addr = qdev->remote_heap_addr;
> +			sargs.heap.size = qdev->remote_heap_size;
> +		}
> +
> +		err = qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE_STATIC, &sargs, session);
> +		if (!err && sargs.heap.size)
> +			qdev->audio_init_mem = true;
> +	}

[Severity: Medium]
What happens if the calling task receives a signal while waiting for the DSP
response in qda_attach_audio_pd()?

The call chain:

  qda_attach_audio_pd()
    qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE_STATIC, &sargs, session)
      qda_fastrpc_invoke_common()
        qda_rpmsg_send_msg()
        qda_rpmsg_wait_for_rsp()
          wait_for_completion_interruptible(&ctx->work)

waits interruptibly after the QDA_FASTRPC_RMID_INIT_CREATE_STATIC message
carrying sargs.heap has already been transmitted to the DSP.

If a signal arrives before the DSP replies, qda_fastrpc_invoke() returns
-ERESTARTSYS while the DSP continues processing the remote heap
initialization. Because err is non-zero, qdev->audio_init_mem remains false
and qdev->remote_heap_lock is released, allowing a restarted syscall or a
concurrent caller to observe !qdev->audio_init_mem and hand the remote heap
to the DSP a second time while the first attach is still in flight or after
the DSP has already accepted it.

> +
> +out_free:
> +	kfree(name);
> +
> +	return err;
> +}

[Severity: Low]
This isn't a bug, but qda_attach_audio_pd() mixes goto out_free error
handling for name with scoped_guard(mutex, &qdev->remote_heap_lock) in the
same function.

Could name be declared with __free(kfree) so that the NUL-termination check
can return -EINVAL directly without mixing goto cleanup and scope-based
cleanup helpers?

> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 7e58df3eb2e17..b93e906b57930 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c

[ ... ]

> @@ -145,6 +147,81 @@ static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
>  	return ret;
>  }
>  
> +static int qda_init_vmperms(struct qda_dev *qdev)
> +{
> +	u32 vmids[QDA_MAX_VMIDS];
> +	int vmcount, i;
> +
> +	vmcount = of_property_read_variable_u32_array(qdev->dev->of_node, "qcom,vmids",
> +						      vmids, 0, QDA_MAX_VMIDS);
> +	if (vmcount == -EINVAL || vmcount == -ENODATA)
> +		return 0;
> +	if (vmcount < 0)
> +		return vmcount;
> +
> +	if (!qcom_scm_is_available())
> +		return -EPROBE_DEFER;
> +
> +	for (i = 0; i < vmcount; i++) {
> +		qdev->vmperms[i].vmid = vmids[i];
> +		qdev->vmperms[i].perm = QCOM_SCM_PERM_RWX;
> +	}
> +	qdev->vmcount = vmcount;
> +
> +	return 0;
> +}
> +
> +/*
> + * The region is optional: without it an Audio PD attach is refused. It is
> + * recorded only once it has been assigned, so teardown hands back exactly
> + * what was handed over.
> + */
> +static int qda_init_remote_heap(struct qda_dev *qdev)
> +{
> +	u64 src_perms = BIT(QCOM_SCM_VMID_HLOS);
> +	struct resource res;
> +	int ret;
> +
> +	if (strcmp(qdev->dsp_name, "adsp") && strcmp(qdev->dsp_name, "sdsp"))
> +		return 0;
> +
> +	if (of_reserved_mem_region_to_resource(qdev->dev->of_node, 0, &res))
> +		return 0;
> +
> +	if (qdev->vmcount) {
> +		ret = qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms,
> +					  qdev->vmperms, qdev->vmcount);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	qdev->remote_heap_addr = res.start;
> +	qdev->remote_heap_size = resource_size(&res);
> +
> +	return 0;
> +}

[Severity: High]
Can qda_init_remote_heap() hyp-assign memory away from HLOS without first
allocating it from CMA?

In the Qualcomm FastRPC device tree bindings and upstream SoC device trees,
the ADSP remote heap memory-region (adsp_rpc_remote_heap_mem) is defined as a
reusable shared-dma-pool without no-map. For a reusable CMA region, those
pages remain in the Linux page allocator for movable HLOS allocations until
allocated via CMA or DMA APIs.

Taking the raw physical range from of_reserved_mem_region_to_resource() in
qda_init_remote_heap() and revoking QCOM_SCM_VMID_HLOS access via
qcom_scm_assign_mem() without allocating the pages first leaves active or
future movable HLOS pages inside that range, which will trigger stage-2
permission faults when accessed by the CPU and corrupt memory when used by
the ADSP.

> +
> +static void qda_deinit_remote_heap(struct qda_dev *qdev)
> +{
> +	struct qcom_scm_vmperm dst_perms = {
> +		.vmid = QCOM_SCM_VMID_HLOS,
> +		.perm = QCOM_SCM_PERM_RWX,
> +	};
> +	u64 src_perms = 0;
> +	int ret, i;
> +
> +	if (!qdev->remote_heap_size || !qdev->vmcount)
> +		return;
> +
> +	for (i = 0; i < qdev->vmcount; i++)
> +		src_perms |= BIT(qdev->vmperms[i].vmid);

[Severity: Medium]
Could BIT(qdev->vmperms[i].vmid) overflow a 32-bit unsigned long or shift past
63?

In qda_deinit_remote_heap() (and in qda_init_remote_heap() when initializing
src_perms), BIT() uses 1UL << nr, whereas src_perms is a u64 and ADSP remote
heap VMIDs such as QCOM_SCM_VMID_ADSP_HEAP (0x25) or
QCOM_SCM_VMID_CP_ADSP_SHARED (0x33) exceed 31. On 32-bit builds (allowed via
ARCH_QCOM || COMPILE_TEST), shifting 1UL by 37 or 51 is undefined behavior and
fails to populate the upper 32 bits of src_perms.

In addition, qda_init_vmperms() copies u32 values from the qcom,vmids property
without checking that each vmid is less than BITS_PER_TYPE(u64). Should
qda_init_remote_heap() and qda_deinit_remote_heap() use BIT_ULL(), and should
qda_init_vmperms() reject VMIDs >= BITS_PER_TYPE(u64)?

> +
> +	ret = qcom_scm_assign_mem(qdev->remote_heap_addr, qdev->remote_heap_size,
> +				  &src_perms, &dst_perms, 1);
> +	if (ret)
> +		dev_err(qdev->dev, "Failed to assign remote heap back to HLOS: %d\n", ret);
> +}
> +
>  static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
>  {
>  	struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
> @@ -163,6 +240,7 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
>  	qda_fastrpc_flush_pending(qdev);
>  	qda_cb_unpopulate(qdev);
>  	qda_deinit_device(qdev);
> +	qda_deinit_remote_heap(qdev);
>  }

[Severity: High]
Can qda_attach_audio_pd() race with qda_rpmsg_remove() and access destroyed
device state after teardown?

If thread A is inside qda_attach_audio_pd() holding qdev->remote_heap_lock
while waiting for the DSP response, a concurrent thread B calling
qda_ioctl_attach() -> qda_attach_audio_pd() can already be blocked waiting to
acquire qdev->remote_heap_lock before allocating an invoke context in
qdev->ctx_xa.

When qda_rpmsg_remove() runs:

  qda_rpmsg_remove()
    drm_dev_unplug(&qdev->drm_dev)
    qdev->rpdev = NULL
    qda_fastrpc_flush_pending(qdev)
    qda_cb_unpopulate(qdev)
    qda_deinit_device(qdev)
    qda_deinit_remote_heap(qdev)

drm_dev_unplug() does not wait for threads already inside drm_ioctl(), and
qda_fastrpc_flush_pending() only wakes thread A. While thread A releases
qdev->remote_heap_lock and thread B acquires it, qda_rpmsg_remove() proceeds
to destroy the context bank platform devices in qda_cb_unpopulate() and free
qdev->iommu_mgr and destroy qdev->ctx_xa in qda_deinit_device().

Thread B then calls qda_fastrpc_invoke(QDA_FASTRPC_RMID_INIT_CREATE_STATIC,
...) without checking whether the device was unplugged, executing
qda_fastrpc_context_alloc() on the destroyed qdev->ctx_xa and
qda_fastrpc_prepare_args_create_static() -> alloc_kernel_gem() ->
qda_gem_create_object() -> qda_memory_manager_alloc() on the destroyed
iommu_mgr and freed context bank device before reaching the drm_dev_enter()
check in qda_rpmsg_send_msg().

> @@ -195,10 +273,20 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
>  		return ret;
>  	}
>  
> -	ret = qda_init_device(qdev, qda_count_cb_nodes(rpdev->dev.of_node));
> +	mutex_init(&qdev->remote_heap_lock);

[Severity: Low]
Unlike qdev->import_lock, which is initialized in qda_alloc_device() and
destroyed in qda_deinit_device(), qdev->remote_heap_lock is initialized here
in qda_rpmsg_probe() and never passed to mutex_destroy() on probe failure or
in qda_deinit_device(). Should remote_heap_lock be initialized and destroyed
alongside import_lock?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=21

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

* Re: [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion
  2026-11-01 22:21 ` [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion Ekansh Gupta
@ 2026-11-01 23:02   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 23:02 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] accel/qda: missing memory ordering on ctx->retval and ctx->is_work_done
- [High] accel/qda: UAF and teardown race in qda_fastrpc_flush_pending()
--

commit ff1bb82ba9e7f31a0c10a5ce3a19f4029819ffbf
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add poll mode for invocation completion

This commit adds per-session completion polling support for dynamically
loaded module invocations in a user PD via DRM_IOCTL_QDA_SET_OPTION and
FASTRPC_IOCTL_SET_OPTION. It reserves a poll word at the end of the
invocation metadata buffer and busy-waits on it before falling back to
the interrupt-based RPMsg response.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index 6044cfad53e96..0617bb4842b52 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c

[ ... ]

> @@ -598,6 +604,54 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
>  	return 0;
>  }
>  
> +static u32 qda_fastrpc_read_poll_addr(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> +	dma_rmb();
> +
> +	return READ_ONCE(*ctx->poll_addr);
> +}
> +
> +/*
> + * Busy-wait for the DSP to write QDA_FASTRPC_POLL_RESPONSE into the poll word,
> + * which saves the interrupt and scheduling latency of waiting for the rpmsg
> + * response. The response still arrives afterwards and drops the reference
> + * qda_rpmsg_send_msg() took for it as usual.
> + *
> + * Return: 0 if the invocation completed, negative error code if it has not
> + * answered within QDA_FASTRPC_POLL_MAX_TIMEOUT_US
> + */
> +static int qda_fastrpc_poll_for_rsp(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> +	u32 val;
> +	int err;
> +
> +	err = read_poll_timeout_atomic(qda_fastrpc_read_poll_addr, val,
> +				       val == QDA_FASTRPC_POLL_RESPONSE ||
> +				       READ_ONCE(ctx->is_work_done),
> +				       1, QDA_FASTRPC_POLL_MAX_TIMEOUT_US, false, ctx);
> +	if (err)
> +		return err;
> +
> +	/* The rpmsg path records its own status, so only the poll path's is set here */
> +	if (val == QDA_FASTRPC_POLL_RESPONSE) {
> +		WRITE_ONCE(ctx->is_work_done, true);
> +		ctx->retval = 0;
> +	}
> +
> +	return 0;
> +}
> +
> +static int qda_fastrpc_wait_for_completion(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> +	if (ctx->is_polled && !qda_fastrpc_poll_for_rsp(ctx))
> +		return 0;
> +
> +	/* Polling timed out, so fall back to waiting for the rpmsg response */
> +	ctx->is_polled = false;
> +
> +	return qda_rpmsg_wait_for_rsp(ctx);
> +}

[Severity: High]
Can a polled invocation observe ctx->is_work_done == true before the store
to ctx->retval is visible, or race on ctx->retval between the poll and RPMsg
paths?

In qda_fastrpc_context_alloc(), ctx->retval is initialized to -1. When
qda_rpmsg_cb() or qda_fastrpc_flush_pending() completes an invocation, it
writes ctx->retval with a plain store and then sets ctx->is_work_done with
WRITE_ONCE():

    ctx->retval = resp_msg->retval;
    WRITE_ONCE(ctx->is_work_done, true);
    complete(&ctx->work);

If read_poll_timeout_atomic() in qda_fastrpc_poll_for_rsp() exits because
READ_ONCE(ctx->is_work_done) became true while val != QDA_FASTRPC_POLL_RESPONSE,
qda_fastrpc_poll_for_rsp() returns 0 and qda_fastrpc_wait_for_completion()
returns 0 immediately without calling qda_rpmsg_wait_for_rsp(ctx), skipping
wait_for_completion_interruptible(&ctx->work).

Because wait_for_completion_interruptible(&ctx->work) is bypassed and there
is no release-acquire ordering between writing ctx->retval and setting
ctx->is_work_done (the dma_rmb() in qda_fastrpc_invoke_common() only orders
reads on the polling CPU, not the two stores on the CPU running
qda_rpmsg_cb() or qda_fastrpc_flush_pending()), couldn't the polling CPU read
the stale initial ctx->retval == -1 (-EPERM) in qda_fastrpc_invoke_common()?

Also, neither qda_rpmsg_cb() nor qda_fastrpc_flush_pending() checks
ctx->is_work_done before writing ctx->retval, so when
qda_fastrpc_poll_for_rsp() sees val == QDA_FASTRPC_POLL_RESPONSE and sets
ctx->retval = 0, a concurrent qda_rpmsg_cb() or qda_fastrpc_flush_pending()
will still write ctx->retval without synchronization against the polling
thread's write or its subsequent read in qda_fastrpc_invoke_common(). Should
the writers and reader use smp_store_release() and smp_load_acquire() (or
atomic state transition) to order and arbitrate updates to ctx->retval?

> @@ -617,6 +671,7 @@ void qda_fastrpc_flush_pending(struct qda_dev *qdev)
>  
>  	xa_for_each(&qdev->ctx_xa, idx, ctx) {
>  		ctx->retval = -EPIPE;
> +		WRITE_ONCE(ctx->is_work_done, true);
>  		complete(&ctx->work);
>  		kref_put(&ctx->refcount, qda_fastrpc_context_free);
>  	}

[Severity: High]
Does qda_fastrpc_flush_pending() risk a use-after-free or premature device
teardown when polled invocations are in flight or have recently completed?

There appear to be two teardown races when qda_rpmsg_remove() runs:

    drm_dev_unplug(&qdev->drm_dev);
    qdev->rpdev = NULL;
    qda_fastrpc_flush_pending(qdev);
    qda_cb_unpopulate(qdev);
    qda_deinit_device(qdev);

First, when a polled invocation finishes via qda_fastrpc_poll_for_rsp(), the
calling thread returns from qda_fastrpc_invoke() and drops its reference via
kref_put(&ctx->refcount, qda_fastrpc_context_free), leaving ctx in
qdev->ctx_xa with refcount == 1 until the RPMsg reply arrives. When
qda_rpmsg_cb() later receives that reply, it schedules ctx->put_work to drop
the final reference from process context, because ctx is only erased from
qdev->ctx_xa inside qda_fastrpc_context_free(). If qda_rpmsg_remove() runs
after qda_rpmsg_cb() schedules ctx->put_work but before
qda_fastrpc_context_put_wq() executes, qda_fastrpc_flush_pending() will find
ctx still in qdev->ctx_xa and drop its refcount to 0, freeing ctx while
ctx->put_work is still pending on system_wq.

Second, qda_fastrpc_poll_for_rsp() runs outside drm_dev_enter(). If a thread
is actively polling in qda_fastrpc_poll_for_rsp() when qda_rpmsg_remove()
runs, qda_fastrpc_flush_pending() sets ctx->is_work_done = true and drops the
RPMsg reference (decrementing refcount from 2 to 1), and qda_rpmsg_remove()
immediately proceeds to qda_cb_unpopulate(qdev) and qda_deinit_device(qdev)
without waiting for the polling thread to exit and drop its final reference.
When the polling thread subsequently runs qda_fastrpc_context_free(), won't
it call xa_erase() on the destroyed qdev->ctx_xa and free DMA buffers against
the destroyed context-bank platform devices?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=25

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

* Re: [PATCH v4 23/25] accel/qda: Add DSP capability query
  2026-11-01 22:21 ` [PATCH v4 23/25] accel/qda: Add DSP capability query Ekansh Gupta
@ 2026-11-01 23:03   ` sashiko-bot
  2026-11-02  8:46   ` Arnd Bergmann
  1 sibling, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 23:03 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] accel/qda: non-zero remote_session_id in qda_fastrpc_invoke_pack() for dsp info
- [Medium] accel/qda: destroyed mutex qdev->attr_lock while held in qda_deinit_device()
--

commit 8c89a0764b44acd4840198a5f29101cbf6dc706a
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add DSP capability query

This commit adds a QDA_QUERY_DSP_CAPABILITIES query type to
DRM_IOCTL_QDA_QUERY to fetch DSP capability attributes through the DSP
utilities handle and cache them on the device for subsequent queries.

> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 1e72dce769b9a..51d8ef10c6c1a 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
> @@ -154,6 +154,7 @@ struct qda_dev *qda_alloc_device(struct device *dev)
>  
>  	INIT_LIST_HEAD(&qdev->cb_devs);
>  	mutex_init(&qdev->import_lock);
> +	mutex_init(&qdev->attr_lock);
>  	xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
>  	atomic64_set(&qdev->ctx_seq, 0);
>  	atomic_set(&qdev->session_id_seq, 0);
> @@ -181,6 +182,7 @@ void qda_deinit_device(struct qda_dev *qdev)
>  	qda_memory_manager_exit(qdev->iommu_mgr);
>  	xa_destroy(&qdev->ctx_xa);
>  	mutex_destroy(&qdev->import_lock);
> +	mutex_destroy(&qdev->attr_lock);
>  }

[Severity: Medium]
Can mutex_destroy(&qdev->attr_lock) in qda_deinit_device() run while
qdev->attr_lock is still held or waited on by qda_get_dsp_capability()?

In qda_get_dsp_capability(), qdev->attr_lock is held across
qda_fastrpc_invoke():

drivers/accel/qda/qda_ioctl.c:qda_get_dsp_capability() {
	guard(mutex)(&qdev->attr_lock);

	if (!qdev->valid_attributes) {
		err = qda_fastrpc_invoke(QDA_FASTRPC_TYPE_GET_DSP_INFO, qdev->dsp_attributes,
					 session);
}

Inside qda_fastrpc_invoke() -> qda_fastrpc_invoke_common(),
qda_rpmsg_send_msg() calls drm_dev_exit() after rpmsg_send(), and then
qda_rpmsg_wait_for_rsp() sleeps in wait_for_completion_interruptible(&ctx->work)
while qdev->attr_lock remains held.

If the remote processor crashes or the rpmsg device is removed before the
DSP replies, qda_rpmsg_remove() runs:

drivers/accel/qda/qda_rpmsg.c:qda_rpmsg_remove() {
	drm_dev_unplug(&qdev->drm_dev);
	qdev->rpdev = NULL;
	qda_fastrpc_flush_pending(qdev);
	qda_cb_unpopulate(qdev);
	qda_deinit_device(qdev);
}

Because the waiting thread already exited the drm_dev_enter() section,
drm_dev_unplug() does not wait for it. qda_fastrpc_flush_pending() wakes
the invocation with complete(&ctx->work) and qda_rpmsg_remove() proceeds
directly to qda_deinit_device() -> mutex_destroy(&qdev->attr_lock) before
the woken thread in qda_get_dsp_capability() has unlocked qdev->attr_lock.

> diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
> index e9688e3ba4048..6044cfad53e96 100644
> --- a/drivers/accel/qda/qda_fastrpc.c
> +++ b/drivers/accel/qda/qda_fastrpc.c

[ ... ]

> @@ -980,6 +987,43 @@ static int qda_fastrpc_prepare_args_create_static(struct qda_fastrpc_invoke_ctx
>  	return 0;
>  }
>  
> +/*
> + * DSP capability query packed layout:
> + *   [0] count	sizeof(u32), the capacity of the array below	IN
> + *   [1] attrs	(QDA_MAX_DSP_ATTRIBUTES - 1) * sizeof(u32)	OUT
> + *
> + * Index 0 of the cached array is reserved, so only the rest is exchanged.
> + */
> +static int qda_fastrpc_prepare_args_get_dsp_info(struct qda_fastrpc_invoke_ctx *ctx)
> +{
> +	const u32 attr_count = QDA_MAX_DSP_ATTRIBUTES - 1;
> +	struct qda_gem_obj *kernel_gem;
> +	int err;
> +
> +	const size_t off_attrs = sizeof(u32);
> +	const size_t total_size = off_attrs + attr_count * sizeof(u32);
> +
> +	ctx->sc = QDA_FASTRPC_SCALARS(0, 1, 1);
> +	ctx->handle = QDA_FASTRPC_DSP_UTILITIES_HANDLE;

[Severity: High]
When ctx->handle is set to QDA_FASTRPC_DSP_UTILITIES_HANDLE here, should
qda_fastrpc_invoke_pack() also set msg->fastrpc.remote_session_id to 0?

Currently, qda_fastrpc_invoke_pack() only checks for QDA_FASTRPC_INIT_HANDLE:

drivers/accel/qda/qda_fastrpc.c:qda_fastrpc_invoke_pack() {
	/* The session has no process yet to identify while one is being created */
	if (ctx->handle == QDA_FASTRPC_INIT_HANDLE)
		msg->fastrpc.remote_session_id = 0;
	else
		msg->fastrpc.remote_session_id = ctx->session->remote_session_id;
}

When userspace issues DRM_IOCTL_QDA_QUERY with QDA_QUERY_DSP_CAPABILITIES
before creating a DSP process on the session, qda_fastrpc_invoke_pack()
will pack the session's non-zero remote_session_id into the RPMsg wire
header for a stateless utilities call that has no remote process context
(whereas drivers/misc/fastrpc.c passes kernel = true in
fastrpc_get_info_from_dsp() so fastrpc_invoke_send() sets msg->client_id = 0).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=23

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

* Re: [PATCH v4 24/25] accel/qda: Add legacy shim support for DSP capability query
  2026-11-01 22:21 ` [PATCH v4 24/25] accel/qda: Add legacy shim support for " Ekansh Gupta
@ 2026-11-01 23:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-01 23:05 UTC (permalink / raw)
  To: Ekansh Gupta; +Cc: dri-devel, media-ci

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] accel/qda: non-zero remote_session_id sent in qda_fastrpc_invoke_pack()
- [Low] accel/qda: commit message references non-existent qda_fastrpc_get_dsp_info()
--

commit a3e1eaffe6784dab5dbbf7ef1443bdf4fdd1f0a5
Author: Ekansh Gupta <ekangupt@qti.qualcomm.com>

accel/qda: Add legacy shim support for DSP capability query

This commit wires FASTRPC_IOCTL_GET_DSP_INFO into the legacy FastRPC
compatibility shim so user space can query DSP capabilities through
/dev/fastrpc-* devices using the shared capability cache.

>     accel/qda: Add legacy shim support for DSP capability query
>     
>     Wire FASTRPC_IOCTL_GET_DSP_INFO into the legacy FastRPC compatibility
>     shim on top of qda_fastrpc_get_dsp_info(), so existing user space can
>     query DSP capabilities through the /dev/fastrpc-* interface exactly
>     as it could with the driver this one replaces.
>     
>     The device-wide capability cache introduced for the native
>     DRM_IOCTL_QDA_QUERY path is reused as-is; the shim only has to
>     translate between the legacy fastrpc_ioctl_capability layout and the
>     shared qda_fastrpc_get_dsp_info() call.
>     
>     Signed-off-by: Ekansh Gupta <ekangupt@qti.qualcomm.com>

[Severity: Low]
This isn't a bug, but the commit message states twice that
FASTRPC_IOCTL_GET_DSP_INFO is wired on top of qda_fastrpc_get_dsp_info().
Should this reference qda_get_dsp_capability() instead, since
legacy_get_dsp_info() in drivers/accel/qda/qda_legacy_ops.c delegates to
qda_get_dsp_capability() and no function named qda_fastrpc_get_dsp_info()
exists in the codebase?

> diff --git a/drivers/accel/qda/qda_legacy_ops.c b/drivers/accel/qda/qda_legacy_ops.c
> index fc5e94c0c9bcd..8d9d597c76c26 100644
> --- a/drivers/accel/qda/qda_legacy_ops.c
> +++ b/drivers/accel/qda/qda_legacy_ops.c
> @@ -188,6 +188,11 @@ static int legacy_invoke(struct qda_session *session, u32 handle, u32 sc,
>  	return qda_fastrpc_invoke_legacy(session->qda_dev, session, handle, sc, args);
>  }
>  
> +static int legacy_get_dsp_info(struct qda_session *session, u32 attribute_id, u32 *capability)
> +{
> +	return qda_get_dsp_capability(session->qda_dev, session, attribute_id, capability);
> +}

[Severity: Medium]
When user space opens /dev/fastrpc-* (which assigns a positive
session->remote_session_id via qda_session_alloc()) and issues
FASTRPC_IOCTL_GET_DSP_INFO before creating a DSP process, will the DSP
accept the capability query with a non-zero msg->fastrpc.remote_session_id?

When qdev->valid_attributes is false, the call chain:

  qda_shim_get_dsp_info()
    legacy_get_dsp_info()
      qda_get_dsp_capability()
        qda_fastrpc_invoke(QDA_FASTRPC_TYPE_GET_DSP_INFO, ...)
          qda_fastrpc_invoke_common()
            qda_fastrpc_invoke_pack()

packs the request with ctx->handle set to QDA_FASTRPC_DSP_UTILITIES_HANDLE.

However, in drivers/accel/qda/qda_fastrpc.c, qda_fastrpc_invoke_pack() only
zeroes remote_session_id for QDA_FASTRPC_INIT_HANDLE:

	/* The session has no process yet to identify while one is being created */
	if (ctx->handle == QDA_FASTRPC_INIT_HANDLE)
		msg->fastrpc.remote_session_id = 0;
	else
		msg->fastrpc.remote_session_id = ctx->session->remote_session_id;

In contrast, in drivers/misc/fastrpc.c, fastrpc_get_info_from_dsp() calls
fastrpc_internal_invoke() with kernel = true, and fastrpc_invoke_send() sets
msg->client_id = 0 whenever kernel is true.

Should qda_fastrpc_invoke_pack() also set msg->fastrpc.remote_session_id = 0
when ctx->handle == QDA_FASTRPC_DSP_UTILITIES_HANDLE so that capability
queries match the legacy driver and do not pass an uninitialized DSP
process's session ID?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-v4-v4-0-88f2f24a5f0b@qti.qualcomm.com?part=24

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

* Re: [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver
  2026-11-01 22:21 ` [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
  2026-11-01 22:39   ` sashiko-bot
@ 2026-11-02  8:42   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:42 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
> +
> +#include "qda_drv.h"
> +
> +/*
> + * Both compatibles the binding defines are matched here. The glymur, hawi,
> + * maili and nord nodes name "qcom,kaanapali-fastrpc" as their fallback
> + * compatible and so are matched by that entry. Per-SoC DSP parameters are
> + * attached as match data by a later patch, once there is something that

So you add empty or non-working code. No, add these once you actually
have complete and working table.

> + * consumes them.
> + */

Drop comment, redundant. Referencing future patches makes no sense in
source code, because there is no "patch" when looking at the code.

> +static const struct of_device_id qda_rpmsg_of_match[] = {
> +	{ .compatible = "qcom,kaanapali-fastrpc" },
> +	{ .compatible = "qcom,fastrpc" },
> +	{ }




Best regards,
Krzysztof

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

* Re: [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap
  2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
  2026-11-01 23:00   ` sashiko-bot
@ 2026-11-02  8:43   ` Arnd Bergmann
  2026-11-02  8:50   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 61+ messages in thread
From: Arnd Bergmann @ 2026-11-02  8:43 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Greg Kroah-Hartman,
	Dave Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Sumit Semwal, Christian König,
	Philipp Stanner, Dmitry Baryshkov, Bjorn Andersson,
	Krzysztof Kozlowski, rob.clark, Jason Gunthorpe
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On Sun, Nov 1, 2026, at 23:21, Ekansh Gupta wrote:

>  struct drm_qda_remote_attach {
>  	__u32 pd_type;
> -	__u32 pad;
> +	__u32 namelen;
> +	__u64 name;
>  };
> 

This is an ABI change in the middle of the series, which isn't
great even though it is harmless as there are no users that
would see the initial ABI.

I'd suggest defining the structure in the final length
from the start.

      Arnd

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

* Re: [PATCH v4 03/25] accel/qda: Add compute context bank devices
  2026-11-01 22:21 ` [PATCH v4 03/25] accel/qda: Add compute context bank devices Ekansh Gupta
  2026-11-01 22:52   ` sashiko-bot
@ 2026-11-02  8:43   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:43 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 6bd977e8d68a..78b5f31974c1 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
> @@ -5,18 +5,18 @@
>  #include <linux/rpmsg.h>
>  #include <drm/drm_print.h>
>  
> +#include "qda_cb.h"
>  #include "qda_drv.h"
> +#include "qda_soc.h"
>  
>  /*
>   * Both compatibles the binding defines are matched here. The glymur, hawi,
>   * maili and nord nodes name "qcom,kaanapali-fastrpc" as their fallback
> - * compatible and so are matched by that entry. Per-SoC DSP parameters are
> - * attached as match data by a later patch, once there is something that
> - * consumes them.

You just added this in previous commit. Why adding invalid
documentation, which is immediately removed?

> + * compatible and so are matched by that entry.



Best regards,
Krzysztof

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

* Re: [PATCH v4 04/25] accel/qda: Assign a context bank to each session
  2026-11-01 22:21 ` [PATCH v4 04/25] accel/qda: Assign a context bank to each session Ekansh Gupta
  2026-11-01 22:39   ` sashiko-bot
@ 2026-11-02  8:45   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:45 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
> +
> +static int qda_count_cb_nodes(struct device_node *parent_node)
> +{
> +	int count = 0;
> +
> +	for_each_available_child_of_node_scoped(parent_node, child)

I don't see a need for scoped loop.

> +		if (of_device_is_compatible(child, "qcom,fastrpc-compute-cb"))
> +			count++;
> +
> +	return count;
>  }



Best regards,
Krzysztof

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

* Re: [PATCH v4 23/25] accel/qda: Add DSP capability query
  2026-11-01 22:21 ` [PATCH v4 23/25] accel/qda: Add DSP capability query Ekansh Gupta
  2026-11-01 23:03   ` sashiko-bot
@ 2026-11-02  8:46   ` Arnd Bergmann
  1 sibling, 0 replies; 61+ messages in thread
From: Arnd Bergmann @ 2026-11-02  8:46 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Greg Kroah-Hartman,
	Dave Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, Sumit Semwal, Christian König,
	Philipp Stanner, Dmitry Baryshkov, Bjorn Andersson,
	Krzysztof Kozlowski, rob.clark, Jason Gunthorpe
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On Sun, Nov 1, 2026, at 23:21, Ekansh Gupta wrote:

>   *
>   * This structure is used with DRM_IOCTL_QDA_QUERY to query device attributes
> - * based on @query_type.
> + * based on @query_type. @dsp_name and @cap share storage, as only one query
> + * is made per call.
>   */
>  struct drm_qda_query {
>  	__u32 query_type;
> -	__u8 dsp_name[16];
> +	union {
> +		__u8 dsp_name[16];
> +		struct {
> +			__u32 attribute_id;
> +			__u32 capability;
> +		} cap;
> +	};
>  };

I think it would be better to make this separate ioctl commands
instead of a multiplexer for each thing you query. ioctl numbers
are cheap, but complexity is not.

    Arnd

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

* Re: [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim
  2026-11-01 22:21 ` [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim Ekansh Gupta
  2026-11-01 22:48   ` sashiko-bot
@ 2026-11-02  8:47   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:47 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
>  	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");

You just added these lines two patches earlier.

You are adding COMPLETELY new driver, so adding completely new code and
then removing it is confusing. It means you added something which was
not needed.


Best regards,
Krzysztof

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

* Re: [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap
  2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
  2026-11-01 23:00   ` sashiko-bot
  2026-11-02  8:43   ` Arnd Bergmann
@ 2026-11-02  8:50   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:50 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
>  
> +/*
> + * The Audio PD keeps the remote heap across daemon restarts, so only the first
> + * attach on a device hands it to the DSP. The lock is held across the attach
> + * so that a concurrent one cannot skip the heap while the first is still in
> + * flight, and the flag is set only once the DSP has accepted it.
> + */
> +static int qda_attach_audio_pd(struct qda_session *session, struct drm_qda_remote_attach *args)
> +{
> +	struct qda_dev *qdev = session->qda_dev;
> +	struct qda_static_pd_args sargs = {};
> +	char *name;
> +	int err;
> +
> +	if (strcmp(qdev->dsp_name, "adsp") || !qdev->remote_heap_size)

That's value from DTB, so where did you document the ABI?

I see such undocumented ABI in multiple places. Usually requirement
would be to document it, but not label. Why label 'ADSP' is not correct?
Why "Audio DSP" would not be correct?

Drop all this label dependency, completely (and do not introduce other
names, like node name).


Best regards,
Krzysztof

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

* Re: [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver
  2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
                   ` (24 preceding siblings ...)
  2026-11-01 22:21 ` [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion Ekansh Gupta
@ 2026-11-02  8:53 ` Krzysztof Kozlowski
  2026-11-02 10:08   ` Ekansh Gupta
  25 siblings, 1 reply; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02  8:53 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 01/11/2026 23:21, Ekansh Gupta wrote:
>  drivers/accel/qda/qda_soc.c            |   66 ++
>  drivers/accel/qda/qda_soc.h            |   61 ++
>  drivers/misc/Kconfig                   |    1 +
>  include/uapi/drm/qda_accel.h           |  282 +++++++
>  33 files changed, 5877 insertions(+), 1 deletion(-)
> ---
> base-commit: 7649ba73f9e7e1e04d0b46213e39c79ae09dd55c
> change-id: 20261102-v4-e8feb321263f

What is happening here? How are we supposed to work with this?

v1: 20260223-qda-firstpost-4ab05249e2cc
v2: 20260519-qda-series-78c2bf0ed78b
v2 again: 20260817-qda-v2-78e2d1f10529

And now completely different one?

This makes the change id completely unusable. b4 diff obviously does not
work. Any tracking of this does not work.

NAK, really, I already complained internally months ago about such
practice, so apparently not enough.

Best regards,
Krzysztof

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

* Re: [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space
  2026-11-01 22:21 ` [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space Ekansh Gupta
  2026-11-01 22:54   ` sashiko-bot
@ 2026-11-02  9:24   ` Konrad Dybcio
  1 sibling, 0 replies; 61+ messages in thread
From: Konrad Dybcio @ 2026-11-02  9:24 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, krzk, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 11/1/26 11:21 PM, Ekansh Gupta wrote:
> From: Ekansh Gupta <ekangupt@qti.qualcomm.com>
> 
> The DSP can only touch memory it has been told about, so user space
> needs a way to make a buffer visible to it ahead of time rather than
> only per call.

[...]

> +struct qda_fastrpc_mem_map_req_msg {
> +	/** @remote_session_id: Session the mapping belongs to */
> +	s32 remote_session_id;
> +	/** @handle: Identifier the DSP tracks the buffer by */
> +	s32 handle;
> +	/** @offset: Offset within the buffer */
> +	s32 offset;

I think this should be 64b too.
> +	/** @flags: Mapping flags (cache attributes, permissions) */
> +	u32 flags;
> +	/** @vaddrin: Address the DSP should map the buffer at, if it can */
> +	u64 vaddrin;
> +	/** @num: Size of the physical page descriptor in bytes */
> +	s32 num;
> +	/** @data_len: Length of additional inline data */
> +	s32 data_len;

And same here (and in the corresponding uapi struct)

Konrad

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

* Re: [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver
  2026-11-02  8:53 ` [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Krzysztof Kozlowski
@ 2026-11-02 10:08   ` Ekansh Gupta
  2026-11-02 10:12     ` Krzysztof Kozlowski
  0 siblings, 1 reply; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-02 10:08 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 02-11-2026 14:23, Krzysztof Kozlowski wrote:
> On 01/11/2026 23:21, Ekansh Gupta wrote:
>>  drivers/accel/qda/qda_soc.c            |   66 ++
>>  drivers/accel/qda/qda_soc.h            |   61 ++
>>  drivers/misc/Kconfig                   |    1 +
>>  include/uapi/drm/qda_accel.h           |  282 +++++++
>>  33 files changed, 5877 insertions(+), 1 deletion(-)
>> ---
>> base-commit: 7649ba73f9e7e1e04d0b46213e39c79ae09dd55c
>> change-id: 20261102-v4-e8feb321263f
> 
> What is happening here? How are we supposed to work with this?
> 
> v1: 20260223-qda-firstpost-4ab05249e2cc
> v2: 20260519-qda-series-78c2bf0ed78b
> v2 again: 20260817-qda-v2-78e2d1f10529
> 
> And now completely different one?
> 
> This makes the change id completely unusable. b4 diff obviously does not
> work. Any tracking of this does not work.
> 
> NAK, really, I already complained internally months ago about such
> practice, so apparently not enough.
> 
> Best regards,
> Krzysztof

Sorry about this. I lost the original b4 branch and re-enrolled the
series for v4, which generated a new change-id. I should have carried
the existing one over.

I have restored the v3 change-id (20260817-qda-v2-78e2d1f10529) on the
branch, and it will stay fixed for all future revisions of this series.
v5 will use it.

Note that v4 is a substantial rework of v3 (the custom bus and iommu
patches are gone, the legacy shim is new and the series is reordered),
so a range-diff against v3 would be of limited use; the "Changes in v4"
section in the cover letter lists what changed and why.

//Ekansh

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

* Re: [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver
  2026-11-02 10:08   ` Ekansh Gupta
@ 2026-11-02 10:12     ` Krzysztof Kozlowski
  2026-11-02 10:52       ` Ekansh Gupta
  0 siblings, 1 reply; 61+ messages in thread
From: Krzysztof Kozlowski @ 2026-11-02 10:12 UTC (permalink / raw)
  To: Ekansh Gupta, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 02/11/2026 11:08, Ekansh Gupta wrote:
> On 02-11-2026 14:23, Krzysztof Kozlowski wrote:
>> On 01/11/2026 23:21, Ekansh Gupta wrote:
>>>  drivers/accel/qda/qda_soc.c            |   66 ++
>>>  drivers/accel/qda/qda_soc.h            |   61 ++
>>>  drivers/misc/Kconfig                   |    1 +
>>>  include/uapi/drm/qda_accel.h           |  282 +++++++
>>>  33 files changed, 5877 insertions(+), 1 deletion(-)
>>> ---
>>> base-commit: 7649ba73f9e7e1e04d0b46213e39c79ae09dd55c
>>> change-id: 20261102-v4-e8feb321263f
>>
>> What is happening here? How are we supposed to work with this?
>>
>> v1: 20260223-qda-firstpost-4ab05249e2cc
>> v2: 20260519-qda-series-78c2bf0ed78b
>> v2 again: 20260817-qda-v2-78e2d1f10529
>>
>> And now completely different one?
>>
>> This makes the change id completely unusable. b4 diff obviously does not
>> work. Any tracking of this does not work.
>>
>> NAK, really, I already complained internally months ago about such
>> practice, so apparently not enough.
>>
>> Best regards,
>> Krzysztof
> 
> Sorry about this. I lost the original b4 branch and re-enrolled the

You lost original branch four times. It's not a coincidence, it's not
random data loss because then after first loss you would solve the
problem. It's a broken workflow.


Best regards,
Krzysztof

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

* Re: [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver
  2026-11-02 10:12     ` Krzysztof Kozlowski
@ 2026-11-02 10:52       ` Ekansh Gupta
  0 siblings, 0 replies; 61+ messages in thread
From: Ekansh Gupta @ 2026-11-02 10:52 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Oded Gabbay, Jonathan Corbet, Shuah Khan,
	Randy Dunlap, Srinivas Kandagatla, Arnd Bergmann,
	Greg Kroah-Hartman, David Airlie, Simona Vetter,
	Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Sumit Semwal,
	Christian König, Philipp Stanner, dmitry.baryshkov,
	andersson, rob.clark, jgg
  Cc: lingtong, cacar, sandheep, Bharath Kumar, Chenna Kesava Raju,
	dri-devel, linux-doc, linux-kernel, linux-arm-msm, linux-media,
	linaro-mm-sig, Bharath Kumar, Chenna Kesava Raju, Ekansh Gupta

On 02-11-2026 15:42, Krzysztof Kozlowski wrote:
> On 02/11/2026 11:08, Ekansh Gupta wrote:
>> On 02-11-2026 14:23, Krzysztof Kozlowski wrote:
>>> On 01/11/2026 23:21, Ekansh Gupta wrote:
>>>>  drivers/accel/qda/qda_soc.c            |   66 ++
>>>>  drivers/accel/qda/qda_soc.h            |   61 ++
>>>>  drivers/misc/Kconfig                   |    1 +
>>>>  include/uapi/drm/qda_accel.h           |  282 +++++++
>>>>  33 files changed, 5877 insertions(+), 1 deletion(-)
>>>> ---
>>>> base-commit: 7649ba73f9e7e1e04d0b46213e39c79ae09dd55c
>>>> change-id: 20261102-v4-e8feb321263f
>>>
>>> What is happening here? How are we supposed to work with this?
>>>
>>> v1: 20260223-qda-firstpost-4ab05249e2cc
>>> v2: 20260519-qda-series-78c2bf0ed78b
>>> v2 again: 20260817-qda-v2-78e2d1f10529
>>>
>>> And now completely different one?
>>>
>>> This makes the change id completely unusable. b4 diff obviously does not
>>> work. Any tracking of this does not work.
>>>
>>> NAK, really, I already complained internally months ago about such
>>> practice, so apparently not enough.
>>>
>>> Best regards,
>>> Krzysztof
>>
>> Sorry about this. I lost the original b4 branch and re-enrolled the
> 
> You lost original branch four times. It's not a coincidence, it's not
> random data loss because then after first loss you would solve the
> problem. It's a broken workflow.
> 
> 
> Best regards,
> Krzysztof

You are right, and I'm not going to call it data loss again. I started
each posting from a new tree and re-enrolled the series every time,
which is what produced a new change-id for every revision. I've changed
how I work so this cannot happen again:
- one long-lived tree and one b4 branch for this series, used for all
future revisions;
- before every b4 send, a check that the change-id is still
20260817-qda-v2-78e2d1f10529.

v5 will continue from that change-id, with the v3 and v4 postings
recorded in its history.

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

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

Thread overview: 61+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-01 22:21 [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
2026-11-01 22:21 ` [PATCH v4 01/25] accel/qda: Add QDA driver documentation Ekansh Gupta
2026-11-01 22:21 ` [PATCH v4 02/25] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
2026-11-01 22:39   ` sashiko-bot
2026-11-02  8:42   ` Krzysztof Kozlowski
2026-11-01 22:21 ` [PATCH v4 03/25] accel/qda: Add compute context bank devices Ekansh Gupta
2026-11-01 22:52   ` sashiko-bot
2026-11-02  8:43   ` Krzysztof Kozlowski
2026-11-01 22:21 ` [PATCH v4 04/25] accel/qda: Assign a context bank to each session Ekansh Gupta
2026-11-01 22:39   ` sashiko-bot
2026-11-02  8:45   ` Krzysztof Kozlowski
2026-11-01 22:21 ` [PATCH v4 05/25] accel/qda: Add UAPI header and DSP query ioctl Ekansh Gupta
2026-11-01 22:21 ` [PATCH v4 06/25] accel/qda: Add buffer allocation for the DSP Ekansh Gupta
2026-11-01 22:51   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 07/25] accel/qda: Add DMA-BUF import Ekansh Gupta
2026-11-01 22:51   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 08/25] accel/qda: Add legacy ops base and auxiliary device Ekansh Gupta
2026-11-01 22:43   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 09/25] accel/qda: Add legacy FastRPC shim skeleton Ekansh Gupta
2026-11-01 22:42   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 10/25] accel/qda: Add FastRPC invocation contexts Ekansh Gupta
2026-11-01 22:44   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 11/25] accel/qda: Add RPMsg send and response dispatch Ekansh Gupta
2026-11-01 22:39   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 12/25] accel/qda: Add invocation argument validation and packing Ekansh Gupta
2026-11-01 22:44   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 13/25] accel/qda: Add the invocation ioctl and teardown on unplug Ekansh Gupta
2026-11-01 22:41   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 14/25] accel/qda: Add invocation through the legacy FastRPC shim Ekansh Gupta
2026-11-01 22:47   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 15/25] accel/qda: Add DSP process creation and release Ekansh Gupta
2026-11-01 22:50   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 16/25] accel/qda: Add DSP process creation and release to the legacy shim Ekansh Gupta
2026-11-01 22:48   ` sashiko-bot
2026-11-02  8:47   ` Krzysztof Kozlowski
2026-11-01 22:21 ` [PATCH v4 17/25] accel/qda: Add remote memory mapping to the DSP address space Ekansh Gupta
2026-11-01 22:54   ` sashiko-bot
2026-11-02  9:24   ` Konrad Dybcio
2026-11-01 22:21 ` [PATCH v4 18/25] accel/qda: Add remote memory unmapping from " Ekansh Gupta
2026-11-01 22:49   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 19/25] accel/qda: Add remote memory map and unmap to the legacy shim Ekansh Gupta
2026-11-01 22:57   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 20/25] accel/qda: Add remote attach to root and sensors protection domains Ekansh Gupta
2026-11-01 22:49   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 21/25] accel/qda: Add Audio protection domain attach and its remote heap Ekansh Gupta
2026-11-01 23:00   ` sashiko-bot
2026-11-02  8:43   ` Arnd Bergmann
2026-11-02  8:50   ` Krzysztof Kozlowski
2026-11-01 22:21 ` [PATCH v4 22/25] accel/qda: Add remote session attach to the legacy shim Ekansh Gupta
2026-11-01 23:00   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 23/25] accel/qda: Add DSP capability query Ekansh Gupta
2026-11-01 23:03   ` sashiko-bot
2026-11-02  8:46   ` Arnd Bergmann
2026-11-01 22:21 ` [PATCH v4 24/25] accel/qda: Add legacy shim support for " Ekansh Gupta
2026-11-01 23:05   ` sashiko-bot
2026-11-01 22:21 ` [PATCH v4 25/25] accel/qda: Add poll mode for invocation completion Ekansh Gupta
2026-11-01 23:02   ` sashiko-bot
2026-11-02  8:53 ` [PATCH v4 00/25] accel/qda: Qualcomm DSP Accelerator driver Krzysztof Kozlowski
2026-11-02 10:08   ` Ekansh Gupta
2026-11-02 10:12     ` Krzysztof Kozlowski
2026-11-02 10:52       ` Ekansh Gupta

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.