* [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver
@ 2026-08-17 4:47 Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 01/15] MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver Ekansh Gupta
` (13 more replies)
0 siblings, 14 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
This patch series introduces the Qualcomm DSP Accelerator (QDA) driver,
a DRM-based accelerator driver for Qualcomm DSPs. The driver provides a
standardized interface for offloading computational tasks to DSPs found
on Qualcomm SoCs, supporting all DSP domains.
The QDA driver implements the FastRPC protocol over the DRM accel
subsystem. It uses the same device-tree node structure as the existing
fastrpc driver in drivers/misc/. The approach for binding the QDA driver
to device-tree nodes while coexisting with the fastrpc driver is an open
item described below.
v1: https://lore.kernel.org/all/20260519-qda-series-v1-0-b2d984c297f8@oss.qualcomm.com/
RFC: https://lore.kernel.org/dri-devel/20260224-qda-firstpost-v1-0-fe46a9c1a046@oss.qualcomm.com/T/
Changes since v1
================
The v1 review raised two architectural objections and one correctness
issue; all three are resolved in v2:
* Christian König (dma-buf maintainer) pointed out that the imported-
buffer path silently assumed the IOMMU maps every buffer as a single
contiguous range, which is not guaranteed. v2 walks the scatterlist
and cleanly rejects non-contiguous imports; contiguous imports (e.g.
CMA DMA-buf heap) are accepted. (patch 11)
* Dmitry Baryshkov objected to three different buffer-passing formats
in the invoke IOCTL (DMA-BUF fd, direct/inline, DMA handle). v2
passes only GEM handles; userspace imports any fd to a GEM handle
with DRM_IOCTL_PRIME_FD_TO_HANDLE before invoking. Packing and
overlap handling are left to userspace. (patch 12)
* The memory manager (patch 07) used a fixed 16-entry array without
justification and leaked the device descriptor on teardown. v2
allocates the array from the DT context-bank count (as Dmitry
suggested) and frees it correctly.
User-space staging branch
=========================
https://github.com/qualcomm/fastrpc/tree/accel/staging
Key Features
============
* Standard DRM accelerator interface via /dev/accel/accelN
* GEM-based buffer management with DMA-BUF import (PRIME)
* IOMMU-based memory isolation using per-process context banks
* FastRPC protocol implementation for DSP communication
* RPMsg transport layer for reliable message passing
* Support for all DSP domains (ADSP, CDSP, SDSP, GDSP)
* DRM IOCTL interface for DSP session management, buffer allocation,
and remote procedure invocation
Architecture
============
1. DRM Accelerator Framework Integration
The driver registers as a DRM accel device, exposing a standard
/dev/accel/accelN character device node. This provides established
DRM infrastructure for device management, file operations, and
IOCTL dispatch.
2. Memory Management
Buffers are managed as GEM objects with PRIME support for DMA-BUF
import. This enables buffer sharing with other DRM drivers (GPU,
camera, video) using standard kernel mechanisms. Only contiguous
imports are accepted; the driver verifies contiguity at import time
rather than assuming it.
3. IOMMU Context Bank Management
IOMMU context banks (CBs) are represented as proper struct device
instances on a custom virtual bus (qda-compute-cb). Each CB device
is registered with the IOMMU subsystem and receives its own IOMMU
domain, enabling per-session address space isolation. The custom
bus was introduced because IOMMU context banks are synthetic
constructs — not real platform devices — and to ensure CB device
lifetime is strictly subordinate to the parent QDA device.
See also: https://lore.kernel.org/all/245d602f-3037-4ae3-9af9-d98f37258aae@oss.qualcomm.com/
4. Memory Manager Architecture
The memory manager maintains a registry of IOMMU devices in an
array sized to the number of context banks described in the device
tree, and coordinates per-process device assignment with reference-
counted lifetime management. The DMA-coherent backend allocates
buffers with SID-prefixed DMA addresses for DSP firmware
compatibility.
5. Transport Layer
RPMsg communication is handled in a dedicated transport layer
(qda_rpmsg.c), separate from the core DRM driver logic.
6. Code Organization
The driver is organized across multiple files (~4800 lines total):
* qda_drv.c: Core driver and DRM integration
* qda_rpmsg.c: RPMsg transport layer
* qda_cb.c: Context bank device management
* qda_compute_bus.c: Custom virtual bus for CB devices
* qda_gem.c: GEM object management
* qda_prime.c: DMA-BUF import (PRIME)
* qda_memory_manager.c: IOMMU device registry and allocation
* qda_memory_dma.c: DMA-coherent allocation backend
* qda_fastrpc.c: FastRPC protocol implementation
* qda_ioctl.c: IOCTL dispatch
7. UAPI Design
The driver exposes DRM-style IOCTLs defined in
include/uapi/drm/qda_accel.h, following DRM UAPI conventions
(__u32/__u64 types, C++ guard, GPL-2.0-only WITH Linux-syscall-note).
Buffer arguments are identified by GEM handles; the driver never
accepts DMA-BUF fds directly in any IOCTL.
Patch Series Organization
==========================
Patch 01: MAINTAINERS entry
Patch 02: Driver documentation (Documentation/accel/qda/)
Patches 03-04: Core driver skeleton and compute bus
Patch 05: iommu: Register qda-compute-cb bus with IOMMU subsystem
Patches 06-07: CB device enumeration and memory manager
Patch 08: QUERY IOCTL and UAPI header
Patches 09-11: GEM buffer management and PRIME import
Patches 12-15: FastRPC protocol (invoke, session create/release,
map/unmap)
Open Items
===========
1. Device-Tree Compatible String
The QDA driver uses the same device-tree node structure and
properties as the existing fastrpc driver in drivers/misc/. A
mechanism is needed to allow the QDA driver to bind to its device
node independently of the fastrpc driver.
The intended coexistence model is: platforms that require the
complete fastrpc feature set continue to use "qcom,fastrpc"; new
platforms where QDA's feature set is sufficient use a QDA-specific
compatible string. New feature development is directed toward QDA.
The options under consideration are:
a) Add a new "qcom,qda" compatible string to the existing
qcom,fastrpc.yaml binding, since the DT node structure and
properties are identical.
b) Introduce a separate qcom,qda.yaml binding that references or
inherits the fastrpc binding properties.
Seeking guidance from DT binding maintainers on the preferred
approach.
2. Privilege Level Management
Currently, daemon processes and user processes have the same access
level as both use the same accel device node. Daemons attach to
privileged DSP protection domains and require higher privilege
levels for system-level operations. Seeking guidance on the best
approach: separate device nodes, capability-based checks, or DRM
master/authentication mechanisms.
3. Audio and Sensors PD Support
The current series does not handle Audio PD and Sensors PD
functionalities. These specialized protection domains require
additional support for real-time constraints and power management.
Interface Compatibility
========================
The QDA driver uses the same device-tree node structure and child node
layout (including "qcom,fastrpc-compute-cb" child nodes) as the
existing fastrpc driver. The underlying FastRPC protocol and DSP
firmware interface are compatible with the existing fastrpc driver,
ensuring that DSP firmware and libraries continue to work without
modification.
References
==========
Previous discussions on this migration:
- https://lkml.org/lkml/2024/6/24/479
- https://lkml.org/lkml/2024/6/21/1252
Testing
=======
The driver has been tested on Qualcomm platforms with:
- Basic FastRPC attach/release operations
- DSP process creation and initialization
- Memory mapping/unmapping operations
- Dynamic invocation with various buffer types
- GEM buffer allocation and mmap
- PRIME buffer import from other subsystems (contiguous buffers)
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Ekansh Gupta (15):
MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver
accel/qda: Add QDA driver documentation
accel/qda: Add initial QDA DRM accelerator driver
accel/qda: Add compute bus for QDA context banks
iommu: Add QDA compute context bank bus to iommu_buses
accel/qda: Create compute context bank devices on QDA compute bus
accel/qda: Add memory manager for CB devices
accel/qda: Add QUERY IOCTL and QDA UAPI header
accel/qda: Add DMA-backed GEM objects and memory manager integration
accel/qda: Add GEM_CREATE and GEM_MMAP_OFFSET IOCTLs
accel/qda: Add PRIME DMA-BUF import support
accel/qda: Add FastRPC invocation support
accel/qda: Add DSP process creation and release
accel/qda: Add remote memory mapping to DSP address space
accel/qda: Add remote memory unmap from DSP address space
Documentation/accel/index.rst | 1 +
Documentation/accel/qda/index.rst | 13 +
Documentation/accel/qda/qda.rst | 191 ++++++
MAINTAINERS | 11 +
drivers/accel/Kconfig | 1 +
drivers/accel/Makefile | 2 +
drivers/accel/qda/Kconfig | 34 ++
drivers/accel/qda/Makefile | 19 +
drivers/accel/qda/qda_cb.c | 125 ++++
drivers/accel/qda/qda_cb.h | 32 +
drivers/accel/qda/qda_compute_bus.c | 80 +++
drivers/accel/qda/qda_drv.c | 144 +++++
drivers/accel/qda/qda_drv.h | 90 +++
drivers/accel/qda/qda_fastrpc.c | 1009 ++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_fastrpc.h | 367 ++++++++++++
drivers/accel/qda/qda_gem.c | 155 +++++
drivers/accel/qda/qda_gem.h | 60 ++
drivers/accel/qda/qda_ioctl.c | 290 +++++++++
drivers/accel/qda/qda_ioctl.h | 19 +
drivers/accel/qda/qda_memory_dma.c | 82 +++
drivers/accel/qda/qda_memory_dma.h | 17 +
drivers/accel/qda/qda_memory_manager.c | 369 ++++++++++++
drivers/accel/qda/qda_memory_manager.h | 85 +++
drivers/accel/qda/qda_prime.c | 167 ++++++
drivers/accel/qda/qda_prime.h | 18 +
drivers/accel/qda/qda_rpmsg.c | 201 +++++++
drivers/accel/qda/qda_rpmsg.h | 26 +
drivers/iommu/iommu.c | 4 +
include/linux/qda_compute_bus.h | 33 ++
include/uapi/drm/qda_accel.h | 242 ++++++++
30 files changed, 3887 insertions(+)
---
base-commit: 5f07a0db7088b4ef4b9a48069a93b9f3e1a33379
change-id: 20260817-qda-v2-78e2d1f10529
Best regards,
--
Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v2 01/15] MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 02/15] accel/qda: Add QDA driver documentation Ekansh Gupta
` (12 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Add a MAINTAINERS entry for the Qualcomm DSP Accelerator (QDA) driver,
covering the driver source under drivers/accel/qda, documentation under
Documentation/accel/qda, and the UAPI header
include/uapi/drm/qda_accel.h. The linux-arm-msm and dri-devel mailing
lists are listed as the relevant review lists.
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
MAINTAINERS | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 912837dd4640..75fa90c137c7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22388,6 +22388,17 @@ 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>
+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/
+F: drivers/accel/qda/
+F: include/linux/qda_compute_bus.h
+F: include/uapi/drm/qda_accel.h
+
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] 18+ messages in thread
* [PATCH v2 02/15] accel/qda: Add QDA driver documentation
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 01/15] MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 03/15] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
` (11 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Add documentation for the Qualcomm DSP Accelerator (QDA) driver under
Documentation/accel/qda/. The documentation covers the driver
architecture, GEM-based buffer management, IOMMU context bank
isolation, and the RPMsg transport layer.
The user-space API section describes the DRM IOCTLs for session
management, GEM buffer allocation, and remote procedure invocation via
the FastRPC protocol, along with a typical application lifecycle
example. Sections for dynamic debug and basic testing are also
included.
Wire the new documentation into the Compute Accelerators index at
Documentation/accel/index.rst.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Explain implicit per-invocation mapping vs persistent REMOTE_MAP
(Dmitry Baryshkov)
- Add a GPUVM section explaining why gpuvm is not used
(Dmitry Baryshkov)
- Expand the memory management section to describe GEM usage and PRIME
interoperability (Dmitry Baryshkov)
- Add links to the open-source user-space library and QAIC compiler, and
document how to build them (Dmitry Baryshkov, Tomeu Vizoso)
---
Documentation/accel/index.rst | 1 +
Documentation/accel/qda/index.rst | 13 +++
Documentation/accel/qda/qda.rst | 191 ++++++++++++++++++++++++++++++++++++++
3 files changed, 205 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..3a9ba4a90803
--- /dev/null
+++ b/Documentation/accel/qda/qda.rst
@@ -0,0 +1,191 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=====================================
+Qualcomm DSP Accelerator (QDA) Driver
+=====================================
+
+Introduction
+============
+
+The QDA driver is a DRM accel driver for Qualcomm's DSPs. It provides a
+DRM accel based interface for Qualcomm DSP offload, supporting workloads
+such as AI inference, computer vision, audio processing, and sensor offload
+on Qualcomm SoCs. It uses the FastRPC protocol and integrates with DRM and
+GEM infrastructure for device and buffer management.
+
+Key Features
+============
+
+* **DRM accel Interface**: Exposes a standard character device node
+ (e.g., ``/dev/accel/accel0``) via the DRM accel subsystem.
+* **FastRPC Protocol**: Implements the FastRPC protocol for communication
+ between the application processor and the DSP.
+* **GEM Buffer Management**: Uses the DRM GEM interface for buffer
+ allocation, lifecycle management, and DMA-BUF import/export.
+* **IOMMU Isolation**: Uses IOMMU context banks to enforce memory isolation
+ between different DSP user sessions.
+* **Modular Design**: Clean separation between the core DRM logic, the
+ memory manager, and the RPMsg-based transport layer.
+
+Architecture
+============
+
+The QDA driver consists of several functional blocks:
+
+1. **Core Driver (``qda_drv``)**: Manages device registration, file operations,
+ and DRM accel integration.
+2. **Memory Manager (``qda_memory_manager``)**: A flexible memory management
+ layer that handles IOMMU context banks. It supports pluggable backends
+ (such as DMA-coherent) to adapt to different SoC memory architectures.
+3. **GEM Subsystem**: Implements the DRM GEM interface for buffer management:
+
+ * **``qda_gem``**: Core GEM object management, including allocation, mmap
+ operations, and buffer lifecycle management.
+ * **``qda_prime``**: PRIME import functionality for DMA-BUF interoperability
+ with other kernel subsystems.
+
+4. **Transport Layer (``qda_rpmsg``)**: Abstraction over the RPMsg framework
+ to handle low-level message passing with the DSP firmware.
+5. **Compute Bus (``qda_compute_bus``)**: A virtual bus that enumerates the
+ compute context banks described in the device tree. IOMMU context banks are
+ synthetic constructs rather than real platform devices, so a dedicated bus
+ is a better fit than a platform driver, and it makes context bank lifetime
+ explicitly subordinate to the parent QDA device.
+6. **FastRPC Core (``qda_fastrpc``)**: Implements the protocol logic for
+ marshalling arguments and handling remote invocations.
+
+User-Space API
+==============
+
+The driver exposes a set of DRM-compliant IOCTLs:
+
+* ``DRM_IOCTL_QDA_QUERY``: Query DSP parameters, such as the DSP name and
+ hardware capabilities, based on a user-specified ``query_type``.
+* ``DRM_IOCTL_QDA_REMOTE_SESSION_CREATE``: Initialize a new process context
+ on the DSP.
+* ``DRM_IOCTL_QDA_REMOTE_INVOKE``: Submit a remote method invocation (the
+ primary execution unit).
+* ``DRM_IOCTL_QDA_GEM_CREATE``: Allocate a GEM buffer object for DSP usage.
+* ``DRM_IOCTL_QDA_GEM_MMAP_OFFSET``: Retrieve mmap offsets for memory mapping.
+* ``DRM_IOCTL_QDA_REMOTE_MAP`` / ``DRM_IOCTL_QDA_REMOTE_MUNMAP``: Map or unmap
+ buffers into the DSP's virtual address space. Each accepts a ``request``
+ field selecting between a legacy operation (``QDA_MAP_REQUEST_LEGACY`` /
+ ``QDA_MUNMAP_REQUEST_LEGACY``) and an attribute-based operation
+ (``QDA_MAP_REQUEST_ATTR`` / ``QDA_MUNMAP_REQUEST_ATTR``).
+
+ Note that ``DRM_IOCTL_QDA_REMOTE_INVOKE`` will automatically perform
+ dynamic, short-lived mappings for buffer arguments on the fly during a
+ single invocation. However, for performance-critical or frequently
+ used buffers, explicitly mapping them persistently via
+ ``DRM_IOCTL_QDA_REMOTE_MAP`` bypasses this dynamic pin/map/unmap overhead
+ on subsequent invocations.
+
+Usage Example
+=============
+
+A typical lifecycle for a user-space application:
+
+1. **Discovery**: Open ``/dev/accel/accel*`` and use
+ ``DRM_IOCTL_QDA_QUERY`` to identify the DSP domain served by that
+ device node.
+2. **Initialization**: Call ``DRM_IOCTL_QDA_REMOTE_SESSION_CREATE`` to
+ establish a session and create a process context on the DSP.
+3. **Memory**: Allocate buffers via ``DRM_IOCTL_QDA_GEM_CREATE`` or import
+ DMA-BUFs (PRIME fd) from other drivers using ``DRM_IOCTL_PRIME_FD_TO_HANDLE``.
+4. **Execution**: Use ``DRM_IOCTL_QDA_REMOTE_INVOKE`` to pass arguments and
+ execute functions on the DSP.
+5. **Cleanup**: Close file descriptors to automatically release resources and
+ detach the session.
+
+Internal Implementation
+=======================
+
+Memory Management & GEM Usage
+-----------------------------
+The QDA driver uses the standard DRM GEM framework to manage memory buffers.
+Native buffers are allocated from coherent DMA memory and represented as GEM
+objects.
+
+The memory manager creates virtual "IOMMU devices" that map to hardware
+context banks, giving each user session an isolated address space. The
+DMA-coherent backend keeps data consistent between the CPU and DSP without
+manual cache maintenance.
+
+PRIME Interoperability
+~~~~~~~~~~~~~~~~~~~~~~
+By integrating with the DRM GEM PRIME interfaces, the QDA driver supports
+zero-copy sharing of memory buffers (DMA-BUFs) with other multimedia and
+compute subsystems on the SoC.
+
+GPUVM
+~~~~~
+While the DRM core provides the ``gpuvm`` helper framework to manage virtual
+address spaces, the QDA driver does not utilize it. This is because the DSP
+virtual address allocation and page table updates are managed entirely by the
+secure kernel running on the Hexagon DSP. The AP-side driver's responsibility
+is strictly limited to mapping physical pages into the SMMU context bank;
+therefore, the extra layer of AP-side VM tracking provided by GPUVM is redundant.
+
+Debugging
+=========
+Diagnostic messages are emitted through dynamic debug. Enable them for the
+whole driver with:
+
+.. code-block:: bash
+
+ echo "module qda +p" > /sys/kernel/debug/dynamic_debug/control
+
+Testing
+=======
+The QDA driver can be exercised using the ``fastrpc_test`` utility from the
+open-source FastRPC userspace library available at `qualcomm/fastrpc
+<https://github.com/qualcomm/fastrpc>`_.
+
+Building Userspace & Tests
+--------------------------
+To satisfy the DRM accel requirement for a fully open-source userspace toolchain,
+the compiler, userspace library, and test suite must be set up using standard
+open-source tools:
+
+1. **Install Compiler**: Install the latest LLVM.
+2. **QAIC Compiler**: The Qualcomm IDL Compiler (QAIC) is fully open-source and
+ available at `qualcomm/QAIC <https://github.com/qualcomm/QAIC>`_.
+3. **Build FastRPC Userspace Library**: Clone and compile the library:
+
+ .. code-block:: bash
+
+ git clone https://github.com/qualcomm/fastrpc.git
+ cd fastrpc
+ ./gitcompile --host=aarch64-linux-gnu
+ make
+ sudo make install
+
+Running the Test Application
+----------------------------
+Once installed, run the test utility on the target device:
+
+.. code-block:: bash
+
+ fastrpc_test -d 3 -U 1 -t linux -a v68
+
+**Options**
+
+``-d domain``
+ Select the DSP domain to run on:
+
+ * ``0`` — ADSP
+ * ``1`` — MDSP
+ * ``2`` — SDSP
+ * ``3`` — CDSP *(default on targets with CDSP)*
+
+``-U unsigned_PD``
+ Select signed or unsigned protection domain:
+
+ * ``0`` — signed PD
+ * ``1`` — unsigned PD *(default)*
+
+``-t target``
+ Target platform: ``android`` or ``linux`` *(default: linux)*
+
+``-a arch_version``
+ DSP architecture version, e.g. ``v68``, ``v75`` *(default: v68)*
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 03/15] accel/qda: Add initial QDA DRM accelerator driver
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 01/15] MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 02/15] accel/qda: Add QDA driver documentation Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 04/15] accel/qda: Add compute bus for QDA context banks Ekansh Gupta
` (10 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Add the skeleton of the Qualcomm DSP Accelerator (QDA) driver, a DRM
accel driver for the Hexagon DSPs found on Qualcomm SoCs.
This patch registers a DRM accel device, exposing a /dev/accel/accelN
character device node, and binds it to the RPMsg channel used to reach
the DSP. Buffer management, IOMMU context banks and the FastRPC
protocol are added by later patches in this series.
qda_drv.c / qda_drv.h define the drm_driver ops table, the per-file
private state (qda_file_priv) and the main device structure (qda_dev),
which embeds drm_device so that it can be recovered with container_of().
qda_rpmsg.c binds to the "qcom,fastrpc" compatible via
module_rpmsg_driver(), reads the DSP domain name from the "label"
device-tree property, and registers the DRM device.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Use module_rpmsg_driver() and drop the qda_rpmsg_register()/
_unregister() wrappers, module_init()/module_exit() and
qda_rpmsg.h entirely (Dmitry Baryshkov)
- Read the "label" property directly into qdev->dsp_name (Dmitry Baryshkov)
- Drop the probe/remove/init log messages (Dmitry Baryshkov)
- Return the result of qda_register_device() directly (Dmitry Baryshkov)
- Clarify the Kconfig help text (Dmitry Baryshkov)
---
drivers/accel/Kconfig | 1 +
drivers/accel/Makefile | 1 +
drivers/accel/qda/Kconfig | 30 ++++++++++++++++
drivers/accel/qda/Makefile | 10 ++++++
drivers/accel/qda/qda_drv.c | 71 ++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_drv.h | 61 +++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_rpmsg.c | 79 +++++++++++++++++++++++++++++++++++++++++++
7 files changed, 253 insertions(+)
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..58c08dd5f389 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_QDA) += qda/
obj-$(CONFIG_DRM_ACCEL_ROCKET) += rocket/
\ No newline at end of file
diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
new file mode 100644
index 000000000000..e679cd00f092
--- /dev/null
+++ b/drivers/accel/qda/Kconfig
@@ -0,0 +1,30 @@
+# 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 provides a modern alternative
+ to the legacy FastRPC driver found in drivers/misc/.
+
+ The interface exposed to userspace is described in
+ include/uapi/drm/qda_accel.h and is used by the FastRPC userspace
+ library at https://github.com/qualcomm/fastrpc.
+
+ 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..9a64f7dfcb6e
--- /dev/null
+++ b/drivers/accel/qda/qda_drv.c
@@ -0,0 +1,71 @@
+// 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 int qda_open(struct drm_device *dev, struct drm_file *file)
+{
+ struct qda_file_priv *qda_file_priv;
+
+ qda_file_priv = kzalloc_obj(*qda_file_priv);
+ if (!qda_file_priv)
+ return -ENOMEM;
+
+ qda_file_priv->qda_dev = qda_dev_from_drm(dev);
+ file->driver_priv = qda_file_priv;
+
+ return 0;
+}
+
+static void qda_postclose(struct drm_device *dev, struct drm_file *file)
+{
+ struct qda_file_priv *qda_file_priv = file->driver_priv;
+
+ kfree(qda_file_priv);
+ file->driver_priv = NULL;
+}
+
+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..4a27fb40c280
--- /dev/null
+++ b/drivers/accel/qda/qda_drv.h
@@ -0,0 +1,61 @@
+/* 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_file_priv - Per-process private data for DRM file
+ */
+struct qda_file_priv {
+ /** @qda_dev: Back-pointer to device structure */
+ struct qda_dev *qda_dev;
+};
+
+/**
+ * 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..6a6e58333a68
--- /dev/null
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -0,0 +1,79 @@
+// 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"
+
+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;
+}
+
+static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ /* Placeholder: responses will be dispatched here */
+ return 0;
+}
+
+static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
+
+ /*
+ * Unplug first so no ioctl or response callback can be in flight, then
+ * fail any invocation still waiting for the now-departed DSP so it
+ * releases the GEM objects it holds.
+ */
+ 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 const struct of_device_id qda_rpmsg_id_table[] = {
+ { .compatible = "qcom,fastrpc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, qda_rpmsg_id_table);
+
+static struct rpmsg_driver qda_rpmsg_driver = {
+ .probe = qda_rpmsg_probe,
+ .remove = qda_rpmsg_remove,
+ .callback = qda_rpmsg_cb,
+ .drv = {
+ .name = "qcom,fastrpc",
+ .of_match_table = qda_rpmsg_id_table,
+ },
+};
+
+module_rpmsg_driver(qda_rpmsg_driver);
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 04/15] accel/qda: Add compute bus for QDA context banks
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (2 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 03/15] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses Ekansh Gupta
` (9 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Introduce a custom virtual bus (qda-compute-cb) for managing IOMMU
context bank (CB) devices used by the QDA driver.
IOMMU context banks are synthetic constructs — they are not real
platform devices and do not appear as children of a platform bus node
in the device tree. Using a platform driver to represent them was
therefore incorrect and introduced a probe-ordering race: device nodes
were created before the RPMsg channel resources were fully initialized,
and because probe runs asynchronously, user-space could open a CB
device and attempt to start a session before the underlying transport
was ready.
The qda-compute-cb bus solves this by allowing the main QDA driver to
create CB devices explicitly and under its own control, making their
lifetime strictly subordinate to the parent qda_dev. The bus provides
a dma_configure callback that calls of_dma_configure() so that each CB
device gets its own IOMMU domain derived from its device-tree node,
enabling per-session memory isolation.
The bus type and the CB device constructor (create_qda_cb_device) are
exported for use by the QDA memory manager.
A hidden Kconfig symbol (DRM_ACCEL_QDA_COMPUTE_BUS) is introduced and
automatically selected by DRM_ACCEL_QDA so that the bus initialisation
runs via postcore_initcall before any QDA device probes.
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
drivers/accel/Makefile | 1 +
drivers/accel/qda/Kconfig | 4 ++
drivers/accel/qda/Makefile | 2 +
drivers/accel/qda/qda_compute_bus.c | 80 +++++++++++++++++++++++++++++++++++++
include/linux/qda_compute_bus.h | 33 +++++++++++++++
5 files changed, 120 insertions(+)
diff --git a/drivers/accel/Makefile b/drivers/accel/Makefile
index 58c08dd5f389..9ed843cd293f 100644
--- a/drivers/accel/Makefile
+++ b/drivers/accel/Makefile
@@ -6,4 +6,5 @@ obj-$(CONFIG_DRM_ACCEL_HABANALABS) += habanalabs/
obj-$(CONFIG_DRM_ACCEL_IVPU) += ivpu/
obj-$(CONFIG_DRM_ACCEL_QAIC) += qaic/
obj-$(CONFIG_DRM_ACCEL_QDA) += qda/
+obj-$(CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS) += qda/
obj-$(CONFIG_DRM_ACCEL_ROCKET) += rocket/
\ No newline at end of file
diff --git a/drivers/accel/qda/Kconfig b/drivers/accel/qda/Kconfig
index e679cd00f092..e288458b63ab 100644
--- a/drivers/accel/qda/Kconfig
+++ b/drivers/accel/qda/Kconfig
@@ -3,11 +3,15 @@
# Qualcomm DSP accelerator driver
#
+config DRM_ACCEL_QDA_COMPUTE_BUS
+ bool
+
config DRM_ACCEL_QDA
tristate "Qualcomm DSP accelerator"
depends on DRM_ACCEL
depends on ARCH_QCOM || COMPILE_TEST
depends on RPMSG
+ select DRM_ACCEL_QDA_COMPUTE_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 dbe809067a8b..424176f652a5 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -8,3 +8,5 @@ obj-$(CONFIG_DRM_ACCEL_QDA) := qda.o
qda-y := \
qda_drv.o \
qda_rpmsg.o
+
+obj-$(CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS) += qda_compute_bus.o
diff --git a/drivers/accel/qda/qda_compute_bus.c b/drivers/accel/qda/qda_compute_bus.c
new file mode 100644
index 000000000000..69ba3c1396b5
--- /dev/null
+++ b/drivers/accel/qda/qda_compute_bus.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/qda_compute_bus.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+
+static int qda_cb_bus_dma_configure(struct device *dev)
+{
+ return of_dma_configure(dev, dev->of_node, true);
+}
+
+const struct bus_type qda_cb_bus_type = {
+ .name = "qda-compute-cb",
+ .dma_configure = qda_cb_bus_dma_configure,
+};
+EXPORT_SYMBOL_GPL(qda_cb_bus_type);
+
+static void release_qda_cb_device(struct device *dev)
+{
+ of_node_put(dev->of_node);
+ kfree(dev);
+}
+
+struct device *qda_cb_device_create(struct device *parent_device, u64 dma_mask,
+ struct device_node *of_node, const char *fmt, ...)
+{
+ struct device *dev;
+ va_list args;
+ char *name;
+ int ret;
+
+ va_start(args, fmt);
+ name = kvasprintf(GFP_KERNEL, fmt, args);
+ va_end(args);
+ if (!name)
+ return ERR_PTR(-ENOMEM);
+
+ dev = kzalloc_obj(*dev);
+ if (!dev) {
+ kfree(name);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dev->release = release_qda_cb_device;
+ dev->bus = &qda_cb_bus_type;
+ dev->parent = parent_device;
+ dev->coherent_dma_mask = dma_mask;
+ dev->dma_mask = &dev->coherent_dma_mask;
+ dev->of_node = of_node_get(of_node);
+
+ dev_set_name(dev, "%s", name);
+ kfree(name);
+
+ ret = device_register(dev);
+ if (ret) {
+ put_device(dev);
+ return ERR_PTR(ret);
+ }
+
+ return dev;
+}
+EXPORT_SYMBOL_GPL(qda_cb_device_create);
+
+static int __init qda_cb_bus_init(void)
+{
+ int err;
+
+ err = bus_register(&qda_cb_bus_type);
+ if (err < 0) {
+ pr_err("qda-compute-cb bus registration failed: %d\n", err);
+ return err;
+ }
+ return 0;
+}
+
+postcore_initcall(qda_cb_bus_init);
diff --git a/include/linux/qda_compute_bus.h b/include/linux/qda_compute_bus.h
new file mode 100644
index 000000000000..cf7913b79741
--- /dev/null
+++ b/include/linux/qda_compute_bus.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_COMPUTE_BUS_H__
+#define __QDA_COMPUTE_BUS_H__
+
+#include <linux/device.h>
+
+/*
+ * Custom bus type for QDA compute context bank (CB) devices
+ *
+ * This bus type is used for manually created CB devices that represent
+ * IOMMU context banks. The custom bus allows proper IOMMU configuration
+ * and device management for these virtual devices.
+ */
+#ifdef CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS
+extern const struct bus_type qda_cb_bus_type;
+
+struct device *qda_cb_device_create(struct device *parent_device, u64 dma_mask,
+ struct device_node *of_node, const char *fmt, ...)
+#else
+static inline struct device *qda_cb_device_create(struct device *parent_device,
+ u64 dma_mask,
+ struct device_node *of_node,
+ const char *fmt, ...)
+{
+ return ERR_PTR(-ENODEV);
+}
+#endif
+
+#endif /* __QDA_COMPUTE_BUS_H__ */
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (3 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 04/15] accel/qda: Add compute bus for QDA context banks Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 7:01 ` Joerg Roedel (AMD)
2026-08-17 4:47 ` [PATCH v2 06/15] accel/qda: Create compute context bank devices on QDA compute bus Ekansh Gupta
` (8 subsequent siblings)
13 siblings, 1 reply; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Register the QDA compute context bank bus (qda-compute-cb) with the
IOMMU subsystem by adding it to the iommu_buses[] array.
The QDA driver creates synthetic devices on this bus to represent
IOMMU context banks (CBs). Each CB device needs its own IOMMU domain
so that the DSP memory manager can enforce per-session address space
isolation. Without this registration, the IOMMU subsystem does not
probe CB devices for IOMMU groups and of_dma_configure() in the bus
dma_configure callback has no IOMMU domain to attach to.
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
drivers/iommu/iommu.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..4c4f7e732773 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -26,6 +26,7 @@
#include <linux/bitops.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/qda_compute_bus.h>
#include <linux/fsl/mc.h>
#include <linux/module.h>
#include <linux/cc_platform.h>
@@ -200,6 +201,9 @@ static const struct bus_type * const iommu_buses[] = {
#ifdef CONFIG_CDX_BUS
&cdx_bus_type,
#endif
+#ifdef CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS
+ &qda_cb_bus_type,
+#endif
};
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 06/15] accel/qda: Create compute context bank devices on QDA compute bus
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (4 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 07/15] accel/qda: Add memory manager for CB devices Ekansh Gupta
` (7 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Introduce the CB (compute context bank) device management layer. Each
DSP domain node in the device tree may contain child nodes with
compatible "qcom,fastrpc-compute-cb", each representing one IOMMU
context bank. Enumerate those child nodes during RPMsg probe and create
a corresponding device on the qda-compute-cb bus for each one, so that
the IOMMU subsystem assigns each of them its own domain and DSP buffer
mappings are isolated per session.
qda_cb_populate() walks the child nodes and creates one CB device each,
unwinding everything it created if any single creation fails.
qda_destroy_cb_device() removes a device from its IOMMU group before
unregistering it so the domain is released cleanly.
CB devices are created before the DRM device is registered and destroyed
after it is unplugged, so user space cannot reach a CB that does not
exist yet, and no ioctl can be in flight while one is torn down.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Use qda_cb_device_create() and pass the name as a format string
instead of formatting into a stack buffer (Dmitry Baryshkov)
- Fail probe on the first CB creation error and unwind, instead of
counting successes and continuing (Dmitry Baryshkov)
- Drop the impossible NULL check in qda_destroy_cb_device()
(Dmitry Baryshkov)
- Use drm_*() logging consistently instead of mixing with dev_*()
(Dmitry Baryshkov)
---
drivers/accel/qda/Makefile | 1 +
drivers/accel/qda/qda_cb.c | 86 +++++++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_cb.h | 32 ++++++++++++++++
drivers/accel/qda/qda_drv.c | 1 +
drivers/accel/qda/qda_drv.h | 3 ++
drivers/accel/qda/qda_rpmsg.c | 16 +++++++-
6 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 424176f652a5..143c9e4e789e 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_DRM_ACCEL_QDA) := qda.o
qda-y := \
+ qda_cb.o \
qda_drv.o \
qda_rpmsg.o
diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
new file mode 100644
index 000000000000..9c5ef32f351e
--- /dev/null
+++ b/drivers/accel/qda/qda_cb.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/dma-mapping.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/iommu.h>
+#include <linux/qda_compute_bus.h>
+#include <linux/slab.h>
+#include <drm/drm_print.h>
+#include "qda_drv.h"
+#include "qda_cb.h"
+
+int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
+{
+ struct qda_cb_dev *entry;
+ struct device *cb_dev;
+ u32 sid = 0;
+
+ if (of_property_read_u32(cb_node, "reg", &sid))
+ drm_info(&qdev->drm_dev, "No stream ID in DT node %pOFn, using 0\n", cb_node);
+
+ cb_dev = qda_cb_device_create(qdev->dev, DMA_BIT_MASK(32), cb_node,
+ "qda-cb-%s-%u", qdev->dsp_name, sid);
+ if (IS_ERR(cb_dev)) {
+ drm_err(&qdev->drm_dev, "Failed to create CB device for SID %u: %ld\n",
+ sid, PTR_ERR(cb_dev));
+ return PTR_ERR(cb_dev);
+ }
+
+ entry = kzalloc_obj(*entry);
+ if (!entry) {
+ qda_destroy_cb_device(cb_dev);
+ return -ENOMEM;
+ }
+
+ entry->dev = cb_dev;
+ list_add_tail(&entry->node, &qdev->cb_devs);
+
+ 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);
+ qda_destroy_cb_device(entry->dev);
+ kfree(entry);
+ }
+}
+
+int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node)
+{
+ struct device_node *child;
+ int ret;
+
+ for_each_child_of_node(parent_node, child) {
+ if (!of_device_is_compatible(child, "qcom,fastrpc-compute-cb"))
+ continue;
+
+ ret = qda_create_cb_device(qdev, child);
+ if (ret) {
+ drm_err(&qdev->drm_dev, "Failed to create CB device for: %s\n",
+ child->name);
+ of_node_put(child);
+ qda_cb_unpopulate(qdev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void qda_destroy_cb_device(struct device *cb_dev)
+{
+ struct iommu_group *group;
+
+ group = iommu_group_get(cb_dev);
+ if (group) {
+ iommu_group_remove_device(cb_dev);
+ iommu_group_put(group);
+ }
+
+ device_unregister(cb_dev);
+}
diff --git a/drivers/accel/qda/qda_cb.h b/drivers/accel/qda/qda_cb.h
new file mode 100644
index 000000000000..bd83d64fa425
--- /dev/null
+++ b/drivers/accel/qda/qda_cb.h
@@ -0,0 +1,32 @@
+/* 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/device.h>
+#include <linux/list.h>
+#include <linux/of.h>
+#include "qda_drv.h"
+
+struct qda_cb_dev {
+ struct list_head node;
+ struct device *dev;
+};
+
+/*
+ * Compute bus (CB) device management
+ */
+int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node);
+void qda_destroy_cb_device(struct device *cb_dev);
+
+/*
+ * Transport-agnostic CB device population/teardown.
+ * Called by any transport layer (RPMsg, etc.) during probe/remove.
+ */
+int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node);
+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 9a64f7dfcb6e..09b6d5ac390b 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -52,6 +52,7 @@ 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 4a27fb40c280..3bd41b9593fa 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -7,6 +7,7 @@
#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>
@@ -37,6 +38,8 @@ 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;
/** @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 6a6e58333a68..723b3e61bf0a 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -5,6 +5,7 @@
#include <linux/rpmsg.h>
#include <drm/drm_print.h>
+#include "qda_cb.h"
#include "qda_drv.h"
static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
@@ -40,6 +41,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)
@@ -57,7 +59,19 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
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)
+ goto err_unpopulate;
+
+ return 0;
+
+err_unpopulate:
+ qda_cb_unpopulate(qdev);
+ return ret;
}
static const struct of_device_id qda_rpmsg_id_table[] = {
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 07/15] accel/qda: Add memory manager for CB devices
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (5 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 06/15] accel/qda: Create compute context bank devices on QDA compute bus Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 6:52 ` Dmitry Baryshkov
2026-08-17 4:47 ` [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header Ekansh Gupta
` (6 subsequent siblings)
13 siblings, 1 reply; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Introduce the QDA memory manager (qda_memory_manager) to track the
IOMMU devices that back each compute context bank (CB).
Each CB device registered on the qda-compute-cb bus is wrapped in a
qda_iommu_device descriptor recording the device pointer and its stream
ID, and stored in the memory manager's registry. Later patches use this
registry to resolve the IOMMU device a session should allocate from.
The registry is a plain array sized to the number of
"qcom,fastrpc-compute-cb" nodes present in the device tree: the RPMsg
probe counts those nodes and passes the count to qda_init_device(),
which allocates the array in qda_memory_manager_init(). The memory
manager is created before CB devices are populated and destroyed after
they are torn down, so no dangling descriptors remain.
qda_cb_setup_device() is called immediately after a CB device is
registered on the bus: it allocates the descriptor, registers it with
the memory manager, and stores it as the CB device's driver data so
that qda_destroy_cb_device() can unregister and free it during teardown.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Replace the XArray with a plain array sized to the device tree's CB
node count instead of a fixed 16-entry table (Dmitry Baryshkov)
- Fold the init_memory_manager()/cleanup_memory_manager() wrappers into
qda_init_device()/qda_deinit_device() (Dmitry Baryshkov)
- Drop the pr_debug() calls (Dmitry Baryshkov)
- Use goto labels to unwind probe failures instead of open-coding the
cleanup at each error site (Dmitry Baryshkov)
---
drivers/accel/qda/Makefile | 1 +
drivers/accel/qda/qda_cb.c | 39 ++++++++++++++
drivers/accel/qda/qda_drv.c | 26 +++++++++
drivers/accel/qda/qda_drv.h | 5 ++
drivers/accel/qda/qda_memory_manager.c | 98 ++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_memory_manager.h | 55 +++++++++++++++++++
drivers/accel/qda/qda_rpmsg.c | 23 +++++++-
7 files changed, 246 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 143c9e4e789e..701fad5ffb50 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_memory_manager.o \
qda_rpmsg.o
obj-$(CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS) += qda_compute_bus.o
diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
index 9c5ef32f351e..cd32d6df6318 100644
--- a/drivers/accel/qda/qda_cb.c
+++ b/drivers/accel/qda/qda_cb.c
@@ -8,13 +8,40 @@
#include <linux/slab.h>
#include <drm/drm_print.h>
#include "qda_drv.h"
+#include "qda_memory_manager.h"
#include "qda_cb.h"
+static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev, u32 sid)
+{
+ 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;
+ iommu_dev->sid = sid;
+
+ ret = qda_memory_manager_register_device(qdev->iommu_mgr, iommu_dev);
+ if (ret) {
+ drm_err(&qdev->drm_dev, "Failed to register IOMMU device: %d\n", ret);
+ kfree(iommu_dev);
+ return ret;
+ }
+
+ dev_set_drvdata(cb_dev, iommu_dev);
+
+ return 0;
+}
+
int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
{
struct qda_cb_dev *entry;
struct device *cb_dev;
u32 sid = 0;
+ int ret;
if (of_property_read_u32(cb_node, "reg", &sid))
drm_info(&qdev->drm_dev, "No stream ID in DT node %pOFn, using 0\n", cb_node);
@@ -27,6 +54,12 @@ int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
return PTR_ERR(cb_dev);
}
+ ret = qda_cb_setup_device(qdev, cb_dev, sid);
+ if (ret) {
+ device_unregister(cb_dev);
+ return ret;
+ }
+
entry = kzalloc_obj(*entry);
if (!entry) {
qda_destroy_cb_device(cb_dev);
@@ -74,8 +107,14 @@ int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node)
void qda_destroy_cb_device(struct device *cb_dev)
{
+ struct qda_iommu_device *iommu_dev;
struct iommu_group *group;
+ iommu_dev = dev_get_drvdata(cb_dev);
+ if (iommu_dev && iommu_dev->qdev->iommu_mgr)
+ qda_memory_manager_unregister_device(iommu_dev->qdev->iommu_mgr,
+ iommu_dev);
+
group = iommu_group_get(cb_dev);
if (group) {
iommu_group_remove_device(cb_dev);
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 09b6d5ac390b..fdc909facd95 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -56,6 +56,32 @@ struct qda_dev *qda_alloc_device(struct device *dev)
return qdev;
}
+void qda_deinit_device(struct qda_dev *qdev)
+{
+ if (qdev->iommu_mgr) {
+ qda_memory_manager_exit(qdev->iommu_mgr);
+ kfree(qdev->iommu_mgr);
+ qdev->iommu_mgr = NULL;
+ }
+}
+
+int qda_init_device(struct qda_dev *qdev, int num_cbs)
+{
+ int ret;
+
+ qdev->iommu_mgr = kzalloc_obj(*qdev->iommu_mgr);
+ 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);
+ kfree(qdev->iommu_mgr);
+ qdev->iommu_mgr = NULL;
+ }
+
+ return ret;
+}
+
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 3bd41b9593fa..690a833d732b 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"
/* Driver identification */
#define QDA_DRIVER_NAME "qda"
@@ -40,6 +41,8 @@ struct qda_dev {
struct device *dev;
/** @cb_devs: Compute context-bank (CB) child devices */
struct list_head cb_devs;
+ /** @iommu_mgr: IOMMU/memory manager instance */
+ struct qda_memory_manager *iommu_mgr;
/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
const char *dsp_name;
};
@@ -59,6 +62,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..b1a80ee77c35
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_manager.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+
+#include <linux/slab.h>
+#include "qda_drv.h"
+#include "qda_memory_manager.h"
+
+static void cleanup_all_memory_devices(struct qda_memory_manager *mem_mgr)
+{
+ int i;
+
+ for (i = 0; i < mem_mgr->num_devices; i++) {
+ struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
+
+ if (!iommu_dev)
+ continue;
+
+ mem_mgr->devices[i] = NULL;
+ kfree(iommu_dev);
+ }
+ mem_mgr->num_devices = 0;
+}
+
+/**
+ * qda_memory_manager_register_device() - Register an IOMMU device
+ * @mem_mgr: Pointer to memory manager
+ * @iommu_dev: Pointer to IOMMU device to register
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_memory_manager_register_device(struct qda_memory_manager *mem_mgr,
+ struct qda_iommu_device *iommu_dev)
+{
+ if (mem_mgr->num_devices >= mem_mgr->max_devices)
+ return -ENOSPC;
+
+ iommu_dev->id = mem_mgr->num_devices;
+ mem_mgr->devices[mem_mgr->num_devices++] = iommu_dev;
+
+ return 0;
+}
+
+/**
+ * qda_memory_manager_unregister_device() - Unregister an IOMMU device
+ * @mem_mgr: Pointer to memory manager
+ * @iommu_dev: Pointer to IOMMU device to unregister
+ *
+ * Removes the device from the registry and frees it.
+ */
+void qda_memory_manager_unregister_device(struct qda_memory_manager *mem_mgr,
+ struct qda_iommu_device *iommu_dev)
+{
+ int i;
+
+ for (i = 0; i < mem_mgr->num_devices; i++) {
+ if (mem_mgr->devices[i] == iommu_dev) {
+ mem_mgr->devices[i] = NULL;
+ break;
+ }
+ }
+
+ kfree(iommu_dev);
+}
+
+/**
+ * qda_memory_manager_init() - Initialize the memory manager
+ * @mem_mgr: Pointer to memory manager structure to initialize
+ * @max_devices: Number of IOMMU context bank devices 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->num_devices = 0;
+ mem_mgr->max_devices = max_devices;
+
+ return 0;
+}
+
+/**
+ * qda_memory_manager_exit() - Clean up the memory manager
+ * @mem_mgr: Pointer to memory manager structure to clean up
+ */
+void qda_memory_manager_exit(struct qda_memory_manager *mem_mgr)
+{
+ cleanup_all_memory_devices(mem_mgr);
+
+ kfree(mem_mgr->devices);
+ mem_mgr->devices = NULL;
+ mem_mgr->max_devices = 0;
+}
diff --git a/drivers/accel/qda/qda_memory_manager.h b/drivers/accel/qda/qda_memory_manager.h
new file mode 100644
index 000000000000..7e38c8a18284
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_manager.h
@@ -0,0 +1,55 @@
+/* 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>
+
+/* Forward declarations */
+struct qda_dev;
+
+/**
+ * struct qda_iommu_device - IOMMU device instance for memory management
+ *
+ * Represents a single IOMMU-enabled device managed by the memory manager.
+ * Each device can be assigned to a specific process session.
+ */
+struct qda_iommu_device {
+ /** @dev: Pointer to the underlying device */
+ struct device *dev;
+ /** @qdev: Back-pointer to the parent QDA device */
+ struct qda_dev *qdev;
+ /** @id: Unique identifier assigned by the memory manager */
+ u32 id;
+ /** @sid: Stream ID for IOMMU transactions */
+ u32 sid;
+};
+
+/**
+ * struct qda_memory_manager - Central memory management coordinator
+ *
+ * Coordinates memory management across multiple IOMMU devices. Maintains
+ * a registry of devices in an array sized to the number of context banks
+ * described in the device tree.
+ */
+struct qda_memory_manager {
+ /** @devices: Array storing registered IOMMU devices */
+ struct qda_iommu_device **devices;
+ /** @num_devices: Number of registered IOMMU devices */
+ int num_devices;
+ /** @max_devices: Capacity of the @devices array */
+ int max_devices;
+};
+
+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);
+
+#endif /* __QDA_MEMORY_MANAGER_H__ */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 723b3e61bf0a..64bf503106d9 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -42,11 +42,25 @@ 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)
+{
+ struct device_node *child;
+ int count = 0;
+
+ for_each_child_of_node(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)
{
struct qda_dev *qdev;
+ int num_cbs;
int ret;
qdev = qda_rpmsg_alloc_and_init_qdev(rpdev);
@@ -59,10 +73,15 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
return ret;
}
- ret = qda_cb_populate(qdev, rpdev->dev.of_node);
+ num_cbs = qda_count_cb_nodes(rpdev->dev.of_node);
+ ret = qda_init_device(qdev, num_cbs);
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)
goto err_unpopulate;
@@ -71,6 +90,8 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
err_unpopulate:
qda_cb_unpopulate(qdev);
+err_deinit:
+ qda_deinit_device(qdev);
return ret;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (6 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 07/15] accel/qda: Add memory manager for CB devices Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 6:58 ` Dmitry Baryshkov
2026-08-17 4:47 ` [PATCH v2 09/15] accel/qda: Add DMA-backed GEM objects and memory manager integration Ekansh Gupta
` (5 subsequent siblings)
13 siblings, 1 reply; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Introduce DRM_IOCTL_QDA_QUERY, a query IOCTL that lets user-space
retrieve information about the DSP a given /dev/accel/accel* node
represents.
The IOCTL takes a query_type selector as input, so it can be extended
to return additional parameters (capabilities, attributes) in the
future without adding new IOCTLs: drm_ioctl() zero-extends the argument
structure, so new fields can be appended to struct drm_qda_query as
long as they go at the end. The first supported query,
QDA_QUERY_DSP_NAME, returns the DSP domain name (e.g. "cdsp", "adsp").
The UAPI header include/uapi/drm/qda_accel.h defines the command number,
the DRM_IOWR IOCTL definition, the query_type values, and struct
drm_qda_query. It follows the standard DRM UAPI conventions: fixed-width
types, a C++ extern "C" guard, and GPL-2.0-only WITH Linux-syscall-note
licensing.
qda_ioctl_query() validates the reserved pad field, dispatches on
query_type, and copies the DSP name from qda_dev.dsp_name into the
user-supplied buffer with strscpy(). Unknown query types are rejected
with -EINVAL.
qda_drv.c registers the qda_ioctls[] table with the drm_driver so the
DRM core dispatches DRM_IOCTL_QDA_QUERY to qda_ioctl_query().
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Add a query_type input selector so the IOCTL can return different
parameters in future, and switch DRM_IOR -> DRM_IOWR so the input
reaches the kernel (Dmitry Baryshkov)
- Reject unknown query types and a non-zero pad with -EINVAL
---
drivers/accel/qda/Makefile | 1 +
drivers/accel/qda/qda_drv.c | 8 +++++++
drivers/accel/qda/qda_ioctl.c | 35 +++++++++++++++++++++++++++
drivers/accel/qda/qda_ioctl.h | 13 ++++++++++
include/uapi/drm/qda_accel.h | 55 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 112 insertions(+)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index 701fad5ffb50..b658dad35fee 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
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index fdc909facd95..e1fd8bfa12d7 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 int qda_open(struct drm_device *dev, struct drm_file *file)
{
@@ -35,11 +37,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..c1d6c9bc0465
--- /dev/null
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <drm/drm_ioctl.h>
+#include <drm/qda_accel.h>
+#include "qda_drv.h"
+#include "qda_ioctl.h"
+
+/**
+ * qda_ioctl_query() - Query DSP device information
+ * @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 drm_qda_query *args = data;
+ struct qda_dev *qdev;
+
+ if (args->pad)
+ return -EINVAL;
+
+ qdev = qda_dev_from_drm(dev);
+
+ switch (args->query_type) {
+ case QDA_QUERY_DSP_NAME:
+ strscpy(args->dsp_name, qdev->dsp_name, sizeof(args->dsp_name));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
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..fe695347762c
--- /dev/null
+++ b/include/uapi/drm/qda_accel.h
@@ -0,0 +1,55 @@
+/* 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
+
+/*
+ * QDA IOCTL command numbers
+ *
+ * These define the command numbers for QDA-specific IOCTLs.
+ * They are used with DRM_COMMAND_BASE to create the full IOCTL numbers.
+ */
+#define DRM_QDA_QUERY 0x00
+
+/*
+ * QDA IOCTL definitions
+ *
+ * These macros define the actual IOCTL numbers used by userspace applications.
+ * They combine the command numbers with DRM_COMMAND_BASE and specify the
+ * data structure and direction (read/write) for each IOCTL.
+ */
+#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)
+ * @pad: Padding for 64-bit alignment (must be zero)
+ * @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;
+ __u32 pad;
+ __u8 dsp_name[16];
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __QDA_ACCEL_H__ */
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 09/15] accel/qda: Add DMA-backed GEM objects and memory manager integration
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (7 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 10/15] accel/qda: Add GEM_CREATE and GEM_MMAP_OFFSET IOCTLs Ekansh Gupta
` (4 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Introduce DMA-coherent buffer management for the QDA driver, wiring
together the GEM subsystem, the IOMMU memory manager, and a DMA
allocation backend.
qda_gem.c / qda_gem.h
Implements the GEM object lifecycle for QDA buffers. Each buffer is
represented by a qda_gem_obj which embeds a drm_gem_object and
carries the kernel virtual address, DMA address, and a pointer to
the IOMMU device that performed the allocation. The .free callback
delegates to the memory manager, and the .mmap callback uses
dma_mmap_coherent() via the DMA backend.
qda_memory_dma.c / qda_memory_dma.h
DMA coherent allocation backend. qda_dma_alloc() calls
dma_alloc_coherent() on the CB device and encodes the stream ID
(SID) in the upper 32 bits of the returned DMA address, following
the Qualcomm FastRPC convention for IOMMU address space tagging.
qda_dma_free() strips the SID prefix before calling
dma_free_coherent().
qda_memory_manager.c
Adds process-to-device assignment: each DRM file (process) is
assigned one IOMMU context bank device for the lifetime of the
session. qda_memory_manager_assign_device() first checks whether
the process already has a device (reusing it with a refcount
increment), then falls back to claiming an unassigned device.
qda_memory_manager_alloc() and qda_memory_manager_free() delegate
to the DMA backend after resolving the correct CB device for the
calling process.
qda_drv.c / qda_drv.h
qda_file_priv gains an assigned_iommu_dev pointer and a pid field.
The .postclose callback decrements the IOMMU device refcount and
clears the process assignment when the last reference is dropped.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Adapt to the dynamically-sized device array introduced in patch 07
(kcalloc'd from DT node count, replaces fixed QDA_IOMMU_DEVICES_MAX)
- Protect register/unregister with the process_assignment_lock mutex so
the device-assignment and device-registration paths are serialised
- No functional changes requested by reviewers on this patch
---
drivers/accel/qda/Makefile | 2 +
drivers/accel/qda/qda_drv.c | 4 +
drivers/accel/qda/qda_drv.h | 4 +
drivers/accel/qda/qda_gem.c | 134 ++++++++++++++++++
drivers/accel/qda/qda_gem.h | 52 +++++++
drivers/accel/qda/qda_memory_dma.c | 82 +++++++++++
drivers/accel/qda/qda_memory_dma.h | 17 +++
drivers/accel/qda/qda_memory_manager.c | 239 ++++++++++++++++++++++++++++++++-
drivers/accel/qda/qda_memory_manager.h | 30 +++++
9 files changed, 559 insertions(+), 5 deletions(-)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index b658dad35fee..a46ddceecfc5 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
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index e1fd8bfa12d7..2b14dab95507 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -21,6 +21,7 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
if (!qda_file_priv)
return -ENOMEM;
+ qda_file_priv->pid = current->pid;
qda_file_priv->qda_dev = qda_dev_from_drm(dev);
file->driver_priv = qda_file_priv;
@@ -31,6 +32,9 @@ static void qda_postclose(struct drm_device *dev, struct drm_file *file)
{
struct qda_file_priv *qda_file_priv = file->driver_priv;
+ if (qda_file_priv->assigned_iommu_dev)
+ qda_memory_manager_release_device(qda_file_priv->assigned_iommu_dev);
+
kfree(qda_file_priv);
file->driver_priv = NULL;
}
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 690a833d732b..2fe58f3efec0 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -24,6 +24,10 @@
struct qda_file_priv {
/** @qda_dev: Back-pointer to device structure */
struct qda_dev *qda_dev;
+ /** @assigned_iommu_dev: IOMMU device assigned to this process */
+ struct qda_iommu_device *assigned_iommu_dev;
+ /** @pid: Process ID for tracking */
+ pid_t pid;
};
/**
diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
new file mode 100644
index 000000000000..66e78013a726
--- /dev/null
+++ b/drivers/accel/qda/qda_gem.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_print.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_manager.h"
+#include "qda_memory_dma.h"
+
+/**
+ * qda_gem_free_object() - Free a GEM object and its associated resources
+ * @gem_obj: DRM GEM object to free
+ */
+void qda_gem_free_object(struct drm_gem_object *gem_obj)
+{
+ struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
+ struct qda_dev *qdev = qda_dev_from_drm(gem_obj->dev);
+
+ if (qda_gem_obj->virt && qdev->iommu_mgr)
+ qda_memory_manager_free(qdev->iommu_mgr, qda_gem_obj);
+
+ drm_gem_object_release(gem_obj);
+ kfree(qda_gem_obj);
+}
+
+/**
+ * qda_gem_mmap_obj() - Map a GEM object into userspace
+ * @drm_obj: DRM GEM object to map
+ * @vma: Virtual memory area to map into
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_gem_mmap_obj(struct drm_gem_object *drm_obj, struct vm_area_struct *vma)
+{
+ struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(drm_obj);
+ int ret;
+
+ /* The fake offset is only used to find the object, not to index 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_alloc_object() - Allocate a new QDA GEM object
+ * @drm_dev: DRM device
+ * @aligned_size: Size of the object in bytes (must be page-aligned)
+ *
+ * Return: Pointer to the new GEM object, or ERR_PTR on failure
+ */
+struct qda_gem_obj *qda_gem_alloc_object(struct drm_device *drm_dev, size_t aligned_size)
+{
+ struct qda_gem_obj *qda_gem_obj;
+ int ret;
+
+ 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;
+
+ return qda_gem_obj;
+}
+
+void qda_gem_cleanup_object(struct qda_gem_obj *qda_gem_obj)
+{
+ drm_gem_object_release(&qda_gem_obj->base);
+ kfree(qda_gem_obj);
+}
+
+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;
+}
+
+/**
+ * qda_gem_create_object() - Allocate and initialize a GEM object with DMA backing
+ * @drm_dev: DRM device
+ * @iommu_mgr: Memory manager to use for DMA allocation
+ * @size: Requested size in bytes
+ * @file_priv: DRM file private data for process association
+ *
+ * Return: Pointer to the base DRM 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 drm_file *file_priv)
+{
+ 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 = qda_gem_alloc_object(drm_dev, aligned_size);
+ if (IS_ERR(qda_gem_obj))
+ return ERR_CAST(qda_gem_obj);
+
+ ret = qda_memory_manager_alloc(iommu_mgr, qda_gem_obj, file_priv);
+ if (ret) {
+ qda_gem_cleanup_object(qda_gem_obj);
+ return ERR_PTR(ret);
+ }
+
+ return &qda_gem_obj->base;
+}
diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
new file mode 100644
index 000000000000..afd7c9b49549
--- /dev/null
+++ b/drivers/accel/qda/qda_gem.h
@@ -0,0 +1,52 @@
+/* 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_gem.h>
+#include "qda_memory_manager.h"
+
+/**
+ * struct qda_gem_obj - QDA GEM buffer object
+ *
+ * Represents a GEM buffer object that can be allocated by the driver
+ * or imported from another driver via DMA-BUF.
+ */
+struct qda_gem_obj {
+ /** @base: DRM GEM object base — must be first member */
+ struct drm_gem_object base;
+ /** @iommu_dev: IOMMU context bank device that performed the allocation */
+ struct qda_iommu_device *iommu_dev;
+ /** @virt: Kernel virtual address of the allocated DMA memory */
+ void *virt;
+ /** @dma_addr: DMA address (with SID encoded in upper 32 bits) */
+ 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)
+
+/* GEM object lifecycle */
+struct drm_gem_object *qda_gem_create_object(struct drm_device *drm_dev,
+ struct qda_memory_manager *iommu_mgr,
+ size_t size, struct drm_file *file_priv);
+void qda_gem_free_object(struct drm_gem_object *gem_obj);
+int qda_gem_mmap_obj(struct drm_gem_object *gem_obj, struct vm_area_struct *vma);
+
+/* Internal helpers (also used by PRIME import) */
+struct qda_gem_obj *qda_gem_alloc_object(struct drm_device *drm_dev, size_t aligned_size);
+void qda_gem_cleanup_object(struct qda_gem_obj *qda_gem_obj);
+
+/* Utility functions */
+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_memory_dma.c b/drivers/accel/qda/qda_memory_dma.c
new file mode 100644
index 000000000000..7072ed8edf0b
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_dma.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_dma.h"
+
+/*
+ * The DSP identifies the IOMMU context bank from the upper bits of the address,
+ * so the stream ID is folded into every address handed to it. The DMA API only
+ * ever sees the plain 32-bit address, which is why the context bank devices are
+ * created with a 32-bit DMA mask.
+ */
+static dma_addr_t get_actual_dma_addr(struct qda_gem_obj *gem_obj)
+{
+ return gem_obj->dma_addr - ((u64)gem_obj->iommu_dev->sid << 32);
+}
+
+static void setup_gem_object(struct qda_gem_obj *gem_obj, void *virt,
+ dma_addr_t dma_addr, struct qda_iommu_device *iommu_dev)
+{
+ gem_obj->virt = virt;
+ gem_obj->dma_addr = dma_addr;
+ gem_obj->iommu_dev = iommu_dev;
+}
+
+static void cleanup_gem_object_fields(struct qda_gem_obj *gem_obj)
+{
+ gem_obj->virt = NULL;
+ gem_obj->dma_addr = 0;
+ gem_obj->iommu_dev = NULL;
+}
+
+/**
+ * qda_dma_alloc() - Allocate DMA coherent memory for a GEM object
+ * @iommu_dev: Pointer to the QDA IOMMU device structure
+ * @gem_obj: Pointer to GEM object to allocate memory for
+ * @size: Size of memory 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;
+
+ dma_addr += ((u64)iommu_dev->sid << 32);
+ setup_gem_object(gem_obj, virt, dma_addr, iommu_dev);
+
+ return 0;
+}
+
+/**
+ * qda_dma_free() - Free DMA coherent memory for a GEM object
+ * @gem_obj: Pointer to GEM object to free memory for
+ */
+void qda_dma_free(struct qda_gem_obj *gem_obj)
+{
+ dma_free_coherent(gem_obj->iommu_dev->dev, gem_obj->size,
+ gem_obj->virt, get_actual_dma_addr(gem_obj));
+
+ cleanup_gem_object_fields(gem_obj);
+}
+
+/**
+ * qda_dma_mmap() - Map DMA memory into userspace
+ * @gem_obj: Pointer to GEM object containing DMA memory
+ * @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,
+ get_actual_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..99352a99dc33
--- /dev/null
+++ b/drivers/accel/qda/qda_memory_dma.h
@@ -0,0 +1,17 @@
+/* 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/dma-mapping.h>
+#include "qda_memory_manager.h"
+
+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 b1a80ee77c35..d47c7419e11d 100644
--- a/drivers/accel/qda/qda_memory_manager.c
+++ b/drivers/accel/qda/qda_memory_manager.c
@@ -1,14 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/refcount.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <drm/drm_file.h>
+#include <drm/drm_print.h>
#include "qda_drv.h"
+#include "qda_gem.h"
#include "qda_memory_manager.h"
+#include "qda_memory_dma.h"
+
+static void iommu_device_free(struct kref *ref)
+{
+ kfree(container_of(ref, struct qda_iommu_device, ref));
+}
static void cleanup_all_memory_devices(struct qda_memory_manager *mem_mgr)
{
int i;
+ mutex_lock(&mem_mgr->process_assignment_lock);
for (i = 0; i < mem_mgr->num_devices; i++) {
struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
@@ -16,9 +28,215 @@ static void cleanup_all_memory_devices(struct qda_memory_manager *mem_mgr)
continue;
mem_mgr->devices[i] = NULL;
- kfree(iommu_dev);
+ kref_put(&iommu_dev->ref, iommu_device_free);
}
mem_mgr->num_devices = 0;
+ mutex_unlock(&mem_mgr->process_assignment_lock);
+}
+
+static void init_iommu_device_fields(struct qda_iommu_device *iommu_dev)
+{
+ spin_lock_init(&iommu_dev->lock);
+ refcount_set(&iommu_dev->users, 0);
+ kref_init(&iommu_dev->ref);
+ iommu_dev->assigned_pid = 0;
+ iommu_dev->assigned_file_priv = NULL;
+}
+
+static struct qda_iommu_device *find_device_for_pid(struct qda_memory_manager *mem_mgr,
+ pid_t pid)
+{
+ struct qda_iommu_device *found_dev = NULL;
+ unsigned long flags;
+ int i;
+
+ for (i = 0; i < mem_mgr->num_devices; i++) {
+ struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
+
+ if (!iommu_dev)
+ continue;
+
+ spin_lock_irqsave(&iommu_dev->lock, flags);
+ if (iommu_dev->assigned_pid == pid) {
+ found_dev = iommu_dev;
+ refcount_inc(&found_dev->users);
+ kref_get(&found_dev->ref);
+ spin_unlock_irqrestore(&iommu_dev->lock, flags);
+ break;
+ }
+ spin_unlock_irqrestore(&iommu_dev->lock, flags);
+ }
+
+ return found_dev;
+}
+
+static struct qda_iommu_device *assign_available_device_to_pid(struct qda_memory_manager *mem_mgr,
+ pid_t pid,
+ struct drm_file *file_priv)
+{
+ struct qda_iommu_device *selected_dev = NULL;
+ unsigned long flags;
+ int i;
+
+ for (i = 0; i < mem_mgr->num_devices; i++) {
+ struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
+
+ if (!iommu_dev)
+ continue;
+
+ spin_lock_irqsave(&iommu_dev->lock, flags);
+ if (iommu_dev->assigned_pid == 0) {
+ iommu_dev->assigned_pid = pid;
+ iommu_dev->assigned_file_priv = file_priv;
+ selected_dev = iommu_dev;
+ refcount_set(&selected_dev->users, 1);
+ kref_get(&selected_dev->ref);
+ spin_unlock_irqrestore(&iommu_dev->lock, flags);
+ break;
+ }
+ spin_unlock_irqrestore(&iommu_dev->lock, flags);
+ }
+
+ return selected_dev;
+}
+
+static struct qda_iommu_device *get_process_iommu_device(struct qda_memory_manager *mem_mgr,
+ struct drm_file *file_priv)
+{
+ struct qda_file_priv *qda_priv;
+
+ if (!file_priv || !file_priv->driver_priv)
+ return NULL;
+
+ qda_priv = (struct qda_file_priv *)file_priv->driver_priv;
+ return qda_priv->assigned_iommu_dev;
+}
+
+/**
+ * qda_memory_manager_assign_device() - Assign an IOMMU device to a process
+ * @mem_mgr: Pointer to memory manager
+ * @file_priv: DRM file private data for process association
+ *
+ * On success the caller owns a reference on the device and must release it
+ * with qda_memory_manager_release_device().
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_memory_manager_assign_device(struct qda_memory_manager *mem_mgr,
+ struct drm_file *file_priv)
+{
+ struct qda_file_priv *qda_priv;
+ struct qda_iommu_device *selected_dev = NULL;
+ int ret = 0;
+ pid_t current_pid;
+
+ if (!file_priv || !file_priv->driver_priv)
+ return -EINVAL;
+
+ qda_priv = (struct qda_file_priv *)file_priv->driver_priv;
+ current_pid = qda_priv->pid;
+
+ mutex_lock(&mem_mgr->process_assignment_lock);
+
+ if (qda_priv->assigned_iommu_dev) {
+ ret = 0;
+ goto unlock_and_return;
+ }
+
+ selected_dev = find_device_for_pid(mem_mgr, current_pid);
+
+ if (selected_dev) {
+ qda_priv->assigned_iommu_dev = selected_dev;
+ goto unlock_and_return;
+ }
+
+ selected_dev = assign_available_device_to_pid(mem_mgr, current_pid, file_priv);
+
+ if (!selected_dev) {
+ drm_dbg_driver(file_priv->minor->dev, "No context bank available for PID %d\n",
+ current_pid);
+ ret = -EBUSY;
+ goto unlock_and_return;
+ }
+
+ qda_priv->assigned_iommu_dev = selected_dev;
+
+unlock_and_return:
+ mutex_unlock(&mem_mgr->process_assignment_lock);
+
+ return ret;
+}
+
+/**
+ * qda_memory_manager_release_device() - Release a process assignment
+ * @iommu_dev: Device previously obtained from qda_memory_manager_assign_device()
+ *
+ * Drops the caller's reference. Once the last process using the device is
+ * gone the assignment is cleared so the device can serve another process.
+ */
+void qda_memory_manager_release_device(struct qda_iommu_device *iommu_dev)
+{
+ unsigned long flags;
+
+ if (refcount_dec_and_test(&iommu_dev->users)) {
+ spin_lock_irqsave(&iommu_dev->lock, flags);
+ iommu_dev->assigned_pid = 0;
+ iommu_dev->assigned_file_priv = NULL;
+ spin_unlock_irqrestore(&iommu_dev->lock, flags);
+ }
+
+ kref_put(&iommu_dev->ref, iommu_device_free);
+}
+
+static struct qda_iommu_device *get_or_assign_iommu_device(struct qda_memory_manager *mem_mgr,
+ struct drm_file *file_priv)
+{
+ struct qda_iommu_device *iommu_dev;
+ int ret;
+
+ iommu_dev = get_process_iommu_device(mem_mgr, file_priv);
+ if (iommu_dev)
+ return iommu_dev;
+
+ ret = qda_memory_manager_assign_device(mem_mgr, file_priv);
+ if (ret)
+ return NULL;
+
+ iommu_dev = get_process_iommu_device(mem_mgr, file_priv);
+ if (iommu_dev)
+ return iommu_dev;
+
+ return NULL;
+}
+
+/**
+ * qda_memory_manager_alloc() - Allocate memory for a GEM object
+ * @mem_mgr: Pointer to memory manager
+ * @gem_obj: Pointer to GEM object to allocate memory for
+ * @file_priv: DRM file private data for process association
+ *
+ * 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 drm_file *file_priv)
+{
+ struct qda_iommu_device *selected_dev;
+
+ selected_dev = get_or_assign_iommu_device(mem_mgr, file_priv);
+ if (!selected_dev)
+ return -EBUSY;
+
+ return qda_dma_alloc(selected_dev, gem_obj, gem_obj->size);
+}
+
+/**
+ * qda_memory_manager_free() - Free memory for a GEM object
+ * @mem_mgr: Pointer to memory manager
+ * @gem_obj: Pointer to GEM object to free memory for
+ */
+void qda_memory_manager_free(struct qda_memory_manager *mem_mgr, struct qda_gem_obj *gem_obj)
+{
+ qda_dma_free(gem_obj);
}
/**
@@ -31,13 +249,21 @@ static void cleanup_all_memory_devices(struct qda_memory_manager *mem_mgr)
int qda_memory_manager_register_device(struct qda_memory_manager *mem_mgr,
struct qda_iommu_device *iommu_dev)
{
- if (mem_mgr->num_devices >= mem_mgr->max_devices)
- return -ENOSPC;
+ int ret = 0;
+ mutex_lock(&mem_mgr->process_assignment_lock);
+ if (mem_mgr->num_devices >= mem_mgr->max_devices) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ init_iommu_device_fields(iommu_dev);
iommu_dev->id = mem_mgr->num_devices;
mem_mgr->devices[mem_mgr->num_devices++] = iommu_dev;
- return 0;
+out:
+ mutex_unlock(&mem_mgr->process_assignment_lock);
+ return ret;
}
/**
@@ -52,14 +278,16 @@ void qda_memory_manager_unregister_device(struct qda_memory_manager *mem_mgr,
{
int i;
+ mutex_lock(&mem_mgr->process_assignment_lock);
for (i = 0; i < mem_mgr->num_devices; i++) {
if (mem_mgr->devices[i] == iommu_dev) {
mem_mgr->devices[i] = NULL;
break;
}
}
+ mutex_unlock(&mem_mgr->process_assignment_lock);
- kfree(iommu_dev);
+ kref_put(&iommu_dev->ref, iommu_device_free);
}
/**
@@ -80,6 +308,7 @@ int qda_memory_manager_init(struct qda_memory_manager *mem_mgr, int max_devices)
mem_mgr->num_devices = 0;
mem_mgr->max_devices = max_devices;
+ mutex_init(&mem_mgr->process_assignment_lock);
return 0;
}
diff --git a/drivers/accel/qda/qda_memory_manager.h b/drivers/accel/qda/qda_memory_manager.h
index 7e38c8a18284..0949c0c213f6 100644
--- a/drivers/accel/qda/qda_memory_manager.h
+++ b/drivers/accel/qda/qda_memory_manager.h
@@ -7,25 +7,44 @@
#define __QDA_MEMORY_MANAGER_H__
#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/spinlock.h>
+#include <drm/drm_file.h>
/* Forward declarations */
struct qda_dev;
+struct qda_gem_obj;
/**
* struct qda_iommu_device - IOMMU device instance for memory management
*
* Represents a single IOMMU-enabled device managed by the memory manager.
* Each device can be assigned to a specific process session.
+ *
+ * The object outlives its registration: open files keep a reference through
+ * @ref, so a device can be unregistered while a process still holds it.
*/
struct qda_iommu_device {
/** @dev: Pointer to the underlying device */
struct device *dev;
/** @qdev: Back-pointer to the parent QDA device */
struct qda_dev *qdev;
+ /** @assigned_file_priv: DRM file private data for the assigned process */
+ struct drm_file *assigned_file_priv;
/** @id: Unique identifier assigned by the memory manager */
u32 id;
/** @sid: Stream ID for IOMMU transactions */
u32 sid;
+ /** @assigned_pid: Process ID of the process assigned to this device */
+ pid_t assigned_pid;
+ /** @users: Number of open files sharing this device */
+ refcount_t users;
+ /** @ref: Reference count controlling when the object is freed */
+ struct kref ref;
+ /** @lock: Spinlock protecting concurrent access to device */
+ spinlock_t lock;
};
/**
@@ -42,6 +61,8 @@ struct qda_memory_manager {
int num_devices;
/** @max_devices: Capacity of the @devices array */
int max_devices;
+ /** @process_assignment_lock: Mutex protecting process-to-device assignments */
+ struct mutex process_assignment_lock;
};
int qda_memory_manager_init(struct qda_memory_manager *mem_mgr, int max_devices);
@@ -51,5 +72,14 @@ 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 drm_file *file_priv);
+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 drm_file *file_priv);
+void qda_memory_manager_free(struct qda_memory_manager *mem_mgr,
+ struct qda_gem_obj *gem_obj);
#endif /* __QDA_MEMORY_MANAGER_H__ */
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 10/15] accel/qda: Add GEM_CREATE and GEM_MMAP_OFFSET IOCTLs
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (8 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 09/15] accel/qda: Add DMA-backed GEM objects and memory manager integration Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 11/15] accel/qda: Add PRIME DMA-BUF import support Ekansh Gupta
` (3 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Expose two new DRM IOCTLs that allow user-space to allocate DMA-backed
GEM buffer objects and retrieve their mmap offsets.
DRM_IOCTL_QDA_GEM_CREATE (drm_qda_gem_create)
Allocates a DMA-coherent GEM buffer of the requested size. The
memory manager assigns an IOMMU context bank to the calling process
on first use and performs the DMA allocation against that bank.
Returns a GEM handle that identifies the buffer for subsequent
operations.
DRM_IOCTL_QDA_GEM_MMAP_OFFSET (drm_qda_gem_mmap_offset)
Returns the mmap offset for an existing GEM handle. The offset can
be passed directly to mmap() to map the buffer into user-space.
Imported DMA-BUF objects are rejected by drm_gem_dumb_map_offset().
DRIVER_GEM is added to driver_features now that GEM IOCTLs are
present.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
drivers/accel/qda/qda_drv.c | 4 +++-
drivers/accel/qda/qda_ioctl.c | 50 +++++++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_ioctl.h | 2 ++
include/uapi/drm/qda_accel.h | 36 +++++++++++++++++++++++++++++++
4 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 2b14dab95507..d22c2c66ffc4 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -43,10 +43,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_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index c1d6c9bc0465..f65325c80a12 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -3,6 +3,7 @@
#include <drm/drm_ioctl.h>
#include <drm/qda_accel.h>
#include "qda_drv.h"
+#include "qda_gem.h"
#include "qda_ioctl.h"
/**
@@ -33,3 +34,52 @@ int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_pr
return 0;
}
+
+/**
+ * qda_ioctl_gem_create() - Create a GEM buffer object
+ * @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 drm_qda_gem_create *args = data;
+ struct drm_gem_object *gem_obj;
+ struct qda_dev *qdev;
+
+ if (args->pad)
+ return -EINVAL;
+
+ qdev = qda_dev_from_drm(dev);
+ if (!qdev->iommu_mgr)
+ return -ENODEV;
+
+ gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr, args->size, file_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 for a GEM object
+ * @dev: DRM device structure
+ * @data: User-space data (struct drm_qda_gem_mmap_offset)
+ * @file_priv: DRM file private data
+ *
+ * Uses drm_gem_dumb_map_offset() which rejects imported dma-buf objects
+ * (mmap of imported objects is not allowed).
+ *
+ * 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/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index fe695347762c..1d699f00e7be 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -19,6 +19,8 @@ extern "C" {
* They are used with DRM_COMMAND_BASE to create the full IOCTL numbers.
*/
#define DRM_QDA_QUERY 0x00
+#define DRM_QDA_GEM_CREATE 0x01
+#define DRM_QDA_GEM_MMAP_OFFSET 0x02
/*
* QDA IOCTL definitions
@@ -29,6 +31,10 @@ extern "C" {
*/
#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
@@ -48,6 +54,36 @@ struct drm_qda_query {
__u8 dsp_name[16];
};
+/**
+ * struct drm_qda_gem_create - GEM buffer object creation parameters
+ * @size: Size of the GEM object to create in bytes (input)
+ * @handle: Allocated GEM handle (output)
+ *
+ * This structure is used with DRM_IOCTL_QDA_GEM_CREATE to allocate
+ * a new GEM buffer object.
+ */
+struct drm_qda_gem_create {
+ __u64 size;
+ __u32 handle;
+ __u32 pad;
+};
+
+/**
+ * struct drm_qda_gem_mmap_offset - GEM object mmap offset query
+ * @offset: mmap offset for the GEM object (output)
+ * @handle: GEM handle (input)
+ * @pad: Padding for 64-bit alignment
+ *
+ * This structure is used with DRM_IOCTL_QDA_GEM_MMAP_OFFSET to retrieve
+ * the mmap offset that can be used with mmap() to map the GEM object into
+ * user space.
+ */
+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] 18+ messages in thread
* [PATCH v2 11/15] accel/qda: Add PRIME DMA-BUF import support
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (9 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 10/15] accel/qda: Add GEM_CREATE and GEM_MMAP_OFFSET IOCTLs Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 12/15] accel/qda: Add FastRPC invocation support Ekansh Gupta
` (2 subsequent siblings)
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Allow user-space to import DMA-BUF file descriptors from other
subsystems (GPU, camera, video) into the QDA driver via the standard
DRM PRIME interface.
qda_prime.c
Implements qda_gem_prime_import(), which is set as the driver's
.gem_prime_import callback. On import it:
1. Short-circuits self-import: if the dma_buf was exported by this
device and is not itself an import, the existing GEM object is
returned with an incremented reference count.
2. Attaches to the dma_buf and maps it with DMA_BIDIRECTIONAL via
dma_buf_map_attachment_unlocked(), obtaining an sg_table whose
DMA addresses are IOMMU virtual addresses in the CB device's
address space.
3. Calls qda_memory_manager_alloc() to record the mapping and encode
the SID in the upper bits of the DMA address, matching the
convention used for natively allocated buffers.
qda_prime_fd_to_handle() wraps drm_gem_prime_fd_to_handle() under
qdev->import_lock, storing the calling file_priv in
qdev->current_import_file_priv so that qda_gem_prime_import() can
retrieve it (the .gem_prime_import callback does not receive
file_priv directly, but the context bank to attach to is per-process).
qda_gem.c
qda_gem_free_object() is extended to handle the imported-buffer
teardown path: unmap the sg_table, detach from the dma_buf, and
release the dma_buf reference.
qda_gem_mmap_obj() rejects mmap requests on imported objects, which
must be mapped through the exporter instead.
qda_memory_manager.c
The DSP is given a single base address per buffer, so only buffers
that are mapped as one contiguous range can be described to it.
qda_memory_manager_map_imported() therefore walks the imported
buffer's scatterlist and rejects any buffer whose entries are not
contiguous; contiguous imports (for example from the CMA DMA-buf
heap) are accepted. Whether an exporter or IOMMU coalesces a buffer
into a single range is not guaranteed, so this is verified rather
than assumed.
qda_memory_manager_free() skips the DMA free path for imported
buffers since the memory is owned by the exporter.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Do not assume the exporter or IOMMU maps an imported buffer as a
single contiguous range; walk the scatterlist and cleanly reject
non-contiguous imports instead (Christian König)
- Document that only contiguous imports (e.g. CMA DMA-buf heap) are
supported
- Return -EBUSY rather than -ENODEV when no context bank is available
---
drivers/accel/qda/Makefile | 1 +
drivers/accel/qda/qda_drv.c | 12 ++-
drivers/accel/qda/qda_drv.h | 4 +
drivers/accel/qda/qda_gem.c | 25 ++++-
drivers/accel/qda/qda_gem.h | 8 ++
drivers/accel/qda/qda_memory_manager.c | 42 +++++++++
drivers/accel/qda/qda_prime.c | 167 +++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_prime.h | 18 ++++
8 files changed, 274 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index a46ddceecfc5..fb092e56d7f3 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -12,6 +12,7 @@ qda-y := \
qda_ioctl.o \
qda_memory_dma.o \
qda_memory_manager.o \
+ qda_prime.o \
qda_rpmsg.o
obj-$(CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS) += qda_compute_bus.o
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index d22c2c66ffc4..a68a07d0ae56 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -7,11 +7,13 @@
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
+#include <drm/drm_prime.h>
#include <drm/drm_print.h>
#include <drm/qda_accel.h>
#include "qda_drv.h"
#include "qda_ioctl.h"
+#include "qda_prime.h"
static int qda_open(struct drm_device *dev, struct drm_file *file)
{
@@ -54,6 +56,8 @@ 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,
+ .prime_fd_to_handle = qda_prime_fd_to_handle,
.name = QDA_DRIVER_NAME,
.desc = "Qualcomm DSP Accelerator Driver",
};
@@ -72,6 +76,7 @@ struct qda_dev *qda_alloc_device(struct device *dev)
void qda_deinit_device(struct qda_dev *qdev)
{
+ mutex_destroy(&qdev->import_lock);
if (qdev->iommu_mgr) {
qda_memory_manager_exit(qdev->iommu_mgr);
kfree(qdev->iommu_mgr);
@@ -83,14 +88,19 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
{
int ret;
+ mutex_init(&qdev->import_lock);
+ qdev->current_import_file_priv = NULL;
qdev->iommu_mgr = kzalloc_obj(*qdev->iommu_mgr);
- if (!qdev->iommu_mgr)
+ if (!qdev->iommu_mgr) {
+ mutex_destroy(&qdev->import_lock);
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);
kfree(qdev->iommu_mgr);
qdev->iommu_mgr = NULL;
+ mutex_destroy(&qdev->import_lock);
}
return ret;
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 2fe58f3efec0..422e70b971b8 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -47,6 +47,10 @@ struct qda_dev {
struct list_head cb_devs;
/** @iommu_mgr: IOMMU/memory manager instance */
struct qda_memory_manager *iommu_mgr;
+ /** @import_lock: Lock protecting prime import context */
+ struct mutex import_lock;
+ /** @current_import_file_priv: Current file_priv during prime import */
+ struct drm_file *current_import_file_priv;
/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
const char *dsp_name;
};
diff --git a/drivers/accel/qda/qda_gem.c b/drivers/accel/qda/qda_gem.c
index 66e78013a726..6f5c54daa951 100644
--- a/drivers/accel/qda/qda_gem.c
+++ b/drivers/accel/qda/qda_gem.c
@@ -9,6 +9,7 @@
#include "qda_gem.h"
#include "qda_memory_manager.h"
#include "qda_memory_dma.h"
+#include "qda_prime.h"
/**
* qda_gem_free_object() - Free a GEM object and its associated resources
@@ -19,8 +20,20 @@ void qda_gem_free_object(struct drm_gem_object *gem_obj)
struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(gem_obj);
struct qda_dev *qdev = qda_dev_from_drm(gem_obj->dev);
- if (qda_gem_obj->virt && qdev->iommu_mgr)
- qda_memory_manager_free(qdev->iommu_mgr, qda_gem_obj);
+ if (qda_gem_obj->is_imported) {
+ if (qda_gem_obj->attachment && qda_gem_obj->sgt)
+ dma_buf_unmap_attachment_unlocked(qda_gem_obj->attachment,
+ qda_gem_obj->sgt, DMA_BIDIRECTIONAL);
+ if (qda_gem_obj->attachment)
+ dma_buf_detach(qda_gem_obj->dma_buf, qda_gem_obj->attachment);
+ if (qda_gem_obj->dma_buf)
+ dma_buf_put(qda_gem_obj->dma_buf);
+ if (qda_gem_obj->iommu_dev && qdev->iommu_mgr)
+ qda_memory_manager_free(qdev->iommu_mgr, qda_gem_obj);
+ } else {
+ if (qda_gem_obj->virt && qdev->iommu_mgr)
+ qda_memory_manager_free(qdev->iommu_mgr, qda_gem_obj);
+ }
drm_gem_object_release(gem_obj);
kfree(qda_gem_obj);
@@ -38,6 +51,10 @@ int qda_gem_mmap_obj(struct drm_gem_object *drm_obj, struct vm_area_struct *vma)
struct qda_gem_obj *qda_gem_obj = to_qda_gem_obj(drm_obj);
int ret;
+ /* Imported dma-buf objects must be mmap'd through the exporter, not the importer */
+ if (qda_gem_obj->is_imported)
+ return -EINVAL;
+
/* The fake offset is only used to find the object, not to index it */
vma->vm_pgoff = 0;
@@ -123,6 +140,10 @@ struct drm_gem_object *qda_gem_create_object(struct drm_device *drm_dev,
qda_gem_obj = qda_gem_alloc_object(drm_dev, aligned_size);
if (IS_ERR(qda_gem_obj))
return ERR_CAST(qda_gem_obj);
+ qda_gem_obj->is_imported = false;
+ qda_gem_obj->dma_buf = NULL;
+ qda_gem_obj->attachment = NULL;
+ qda_gem_obj->sgt = NULL;
ret = qda_memory_manager_alloc(iommu_mgr, qda_gem_obj, file_priv);
if (ret) {
diff --git a/drivers/accel/qda/qda_gem.h b/drivers/accel/qda/qda_gem.h
index afd7c9b49549..3e28d14099fb 100644
--- a/drivers/accel/qda/qda_gem.h
+++ b/drivers/accel/qda/qda_gem.h
@@ -21,12 +21,20 @@ struct qda_gem_obj {
struct drm_gem_object base;
/** @iommu_dev: IOMMU context bank device that performed the allocation */
struct qda_iommu_device *iommu_dev;
+ /** @dma_buf: Reference to imported dma_buf */
+ struct dma_buf *dma_buf;
+ /** @attachment: DMA buf attachment */
+ struct dma_buf_attachment *attachment;
+ /** @sgt: Scatter-gather table */
+ struct sg_table *sgt;
/** @virt: Kernel virtual address of the allocated DMA memory */
void *virt;
/** @dma_addr: DMA address (with SID encoded in upper 32 bits) */
dma_addr_t dma_addr;
/** @size: Size of the buffer in bytes */
size_t size;
+ /** @is_imported: True if buffer is imported, false if allocated */
+ bool is_imported;
};
/**
diff --git a/drivers/accel/qda/qda_memory_manager.c b/drivers/accel/qda/qda_memory_manager.c
index d47c7419e11d..3c25ec47cd09 100644
--- a/drivers/accel/qda/qda_memory_manager.c
+++ b/drivers/accel/qda/qda_memory_manager.c
@@ -209,6 +209,41 @@ static struct qda_iommu_device *get_or_assign_iommu_device(struct qda_memory_man
return NULL;
}
+static int qda_memory_manager_map_imported(struct qda_gem_obj *gem_obj,
+ struct qda_iommu_device *iommu_dev)
+{
+ dma_addr_t dma_addr, expected_addr = 0;
+ struct scatterlist *sg, *s;
+ int i;
+
+ sg = gem_obj->sgt->sgl;
+
+ /*
+ * Verify that the imported DMA-BUF is contiguous. While some platforms
+ * or IOMMUs might map buffers as a single range, we must not silently
+ * assume this is always the case. We loop through the scatterlist
+ * entries and reject any that are non-contiguous.
+ */
+ for_each_sg(gem_obj->sgt->sgl, s, gem_obj->sgt->nents, i) {
+ dma_addr_t curr_addr = sg_dma_address(s);
+
+ if (i > 0 && curr_addr != expected_addr) {
+ drm_err_ratelimited(gem_obj->base.dev,
+ "Imported DMA-BUF is non-contiguous; rejecting mapping\n");
+ return -EINVAL;
+ }
+ expected_addr = curr_addr + sg_dma_len(s);
+ }
+
+ gem_obj->iommu_dev = iommu_dev;
+
+ dma_addr = sg_dma_address(sg);
+ dma_addr += ((u64)iommu_dev->sid << 32);
+ gem_obj->dma_addr = dma_addr;
+
+ return 0;
+}
+
/**
* qda_memory_manager_alloc() - Allocate memory for a GEM object
* @mem_mgr: Pointer to memory manager
@@ -226,6 +261,9 @@ int qda_memory_manager_alloc(struct qda_memory_manager *mem_mgr, struct qda_gem_
if (!selected_dev)
return -EBUSY;
+ if (gem_obj->is_imported)
+ return qda_memory_manager_map_imported(gem_obj, selected_dev);
+
return qda_dma_alloc(selected_dev, gem_obj, gem_obj->size);
}
@@ -236,6 +274,10 @@ int qda_memory_manager_alloc(struct qda_memory_manager *mem_mgr, struct qda_gem_
*/
void qda_memory_manager_free(struct qda_memory_manager *mem_mgr, struct qda_gem_obj *gem_obj)
{
+ /* Imported buffers are unmapped through the exporter, not here */
+ if (gem_obj->is_imported)
+ return;
+
qda_dma_free(gem_obj);
}
diff --git a/drivers/accel/qda/qda_prime.c b/drivers/accel/qda/qda_prime.c
new file mode 100644
index 000000000000..306b3037c19c
--- /dev/null
+++ b/drivers/accel/qda/qda_prime.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <drm/drm_gem.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_print.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_prime.h"
+#include "qda_memory_manager.h"
+
+static struct drm_gem_object *check_own_buffer(struct drm_device *dev, struct dma_buf *dma_buf)
+{
+ struct drm_gem_object *existing_gem;
+
+ /* Only safe to access priv if this dma-buf was exported by this device */
+ if (!drm_gem_is_prime_exported_dma_buf(dev, dma_buf))
+ return NULL;
+
+ existing_gem = dma_buf->priv;
+ if (existing_gem->dev != dev)
+ return NULL;
+
+ if (to_qda_gem_obj(existing_gem)->is_imported)
+ return NULL;
+
+ drm_gem_object_get(existing_gem);
+ return existing_gem;
+}
+
+static struct qda_iommu_device *get_iommu_device_for_import(struct qda_dev *qdev,
+ struct drm_file **file_priv_out)
+{
+ struct drm_file *file_priv;
+ struct qda_file_priv *qda_file_priv;
+ struct qda_iommu_device *iommu_dev;
+
+ file_priv = qdev->current_import_file_priv;
+ *file_priv_out = file_priv;
+
+ if (!file_priv || !file_priv->driver_priv)
+ return NULL;
+
+ qda_file_priv = (struct qda_file_priv *)file_priv->driver_priv;
+ iommu_dev = qda_file_priv->assigned_iommu_dev;
+
+ if (!iommu_dev) {
+ if (qda_memory_manager_assign_device(qdev->iommu_mgr, file_priv))
+ return NULL;
+
+ iommu_dev = qda_file_priv->assigned_iommu_dev;
+ }
+
+ return iommu_dev;
+}
+
+static int setup_dma_buf_mapping(struct qda_gem_obj *qda_gem_obj, struct dma_buf *dma_buf,
+ struct device *attach_dev)
+{
+ struct dma_buf_attachment *attachment;
+ struct sg_table *sgt;
+ int ret;
+
+ attachment = dma_buf_attach(dma_buf, attach_dev);
+ if (IS_ERR(attachment))
+ return PTR_ERR(attachment);
+
+ qda_gem_obj->attachment = attachment;
+
+ sgt = dma_buf_map_attachment_unlocked(attachment, DMA_BIDIRECTIONAL);
+ if (IS_ERR(sgt)) {
+ ret = PTR_ERR(sgt);
+ dma_buf_detach(dma_buf, attachment);
+ return ret;
+ }
+ qda_gem_obj->sgt = sgt;
+
+ return 0;
+}
+
+/**
+ * qda_gem_prime_import() - Import a DMA-BUF as a GEM object
+ * @dev: DRM device structure
+ * @dma_buf: DMA-BUF to import
+ *
+ * Return: Pointer to the imported GEM object on success, ERR_PTR on failure
+ */
+struct drm_gem_object *qda_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf)
+{
+ struct qda_dev *qdev = qda_dev_from_drm(dev);
+ struct qda_gem_obj *qda_gem_obj;
+ struct drm_file *file_priv;
+ struct qda_iommu_device *iommu_dev;
+ struct drm_gem_object *existing_gem;
+ size_t aligned_size;
+ int ret;
+
+ existing_gem = check_own_buffer(dev, dma_buf);
+ if (existing_gem)
+ return existing_gem;
+
+ iommu_dev = get_iommu_device_for_import(qdev, &file_priv);
+ if (!iommu_dev || !iommu_dev->dev) {
+ drm_dbg_driver(dev, "No context bank available for prime import\n");
+ return ERR_PTR(-EBUSY);
+ }
+
+ aligned_size = PAGE_ALIGN(dma_buf->size);
+ qda_gem_obj = qda_gem_alloc_object(dev, aligned_size);
+ if (IS_ERR(qda_gem_obj))
+ return ERR_CAST(qda_gem_obj);
+
+ qda_gem_obj->is_imported = true;
+ qda_gem_obj->dma_buf = dma_buf;
+ qda_gem_obj->virt = NULL;
+ qda_gem_obj->iommu_dev = iommu_dev;
+
+ get_dma_buf(dma_buf);
+
+ ret = setup_dma_buf_mapping(qda_gem_obj, dma_buf, iommu_dev->dev);
+ if (ret)
+ goto err_put_dma_buf;
+
+ ret = qda_memory_manager_alloc(qdev->iommu_mgr, qda_gem_obj, file_priv);
+ if (ret)
+ goto err_unmap;
+
+ return &qda_gem_obj->base;
+
+err_unmap:
+ dma_buf_unmap_attachment_unlocked(qda_gem_obj->attachment,
+ qda_gem_obj->sgt, DMA_BIDIRECTIONAL);
+ dma_buf_detach(dma_buf, qda_gem_obj->attachment);
+err_put_dma_buf:
+ dma_buf_put(dma_buf);
+ qda_gem_cleanup_object(qda_gem_obj);
+ return ERR_PTR(ret);
+}
+
+/**
+ * qda_prime_fd_to_handle() - Convert a PRIME fd to a GEM handle
+ * @dev: DRM device structure
+ * @file_priv: DRM file private data
+ * @prime_fd: File descriptor of the PRIME buffer
+ * @handle: Output GEM handle
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_prime_fd_to_handle(struct drm_device *dev, struct drm_file *file_priv,
+ int prime_fd, u32 *handle)
+{
+ struct qda_dev *qdev = qda_dev_from_drm(dev);
+ int ret;
+
+ mutex_lock(&qdev->import_lock);
+ qdev->current_import_file_priv = file_priv;
+
+ ret = drm_gem_prime_fd_to_handle(dev, file_priv, prime_fd, handle);
+
+ qdev->current_import_file_priv = NULL;
+ mutex_unlock(&qdev->import_lock);
+
+ 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..9b3850d54fa7
--- /dev/null
+++ b/drivers/accel/qda/qda_prime.h
@@ -0,0 +1,18 @@
+/* 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>
+#include <linux/dma-buf.h>
+
+struct drm_gem_object *qda_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf);
+int qda_prime_fd_to_handle(struct drm_device *dev, struct drm_file *file_priv,
+ int prime_fd, u32 *handle);
+
+#endif /* __QDA_PRIME_H__ */
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 12/15] accel/qda: Add FastRPC invocation support
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (10 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 11/15] accel/qda: Add PRIME DMA-BUF import support Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 13/15] accel/qda: Add DSP process creation and release Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 14/15] accel/qda: Add remote memory mapping to DSP address space Ekansh Gupta
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Implement the FastRPC remote procedure call path, allowing user-space to
invoke methods on the DSP via DRM_IOCTL_QDA_REMOTE_INVOKE.
qda_fastrpc.c / qda_fastrpc.h
Implements the FastRPC protocol layer: argument marshalling
(qda_fastrpc_invoke_pack), response unmarshalling
(qda_fastrpc_invoke_unpack), and invocation context lifecycle
management. Each invocation allocates a qda_fastrpc_invoke_ctx which
tracks buffer descriptors, GEM objects, and the completion used to
synchronise with the DSP response.
Buffer arguments are identified by GEM handles. Userspace imports any
DMA-BUF fd to a GEM handle with DRM_IOCTL_PRIME_FD_TO_HANDLE before
invoking; the driver never accepts DMA-BUF fds directly. Each argument
is described by its GEM handle, the user virtual address of the data
and its length, from which the driver derives the offset within the
buffer and the page-aligned range to describe to the DSP. Packing
several arguments into one buffer, and any overlap handling, is left
to user space.
qda_rpmsg.c
Implements qda_rpmsg_send_msg() which sends the wire-format
fastrpc_msg (embedded as the first member of qda_msg) directly via
rpmsg_send(), and qda_rpmsg_wait_for_rsp() which blocks on the context
completion. The RPMsg callback dispatches responses to waiting
contexts via the ctx_xa XArray.
qda_ioctl.c
qda_ioctl_invoke() drives the full invocation lifecycle: it builds the
invocation context from the user-supplied arguments, packs the
arguments into the message buffer, sends the message to the DSP, waits
for the response, unpacks the output arguments back to user space and
releases the context.
include/uapi/drm/qda_accel.h
Adds DRM_IOCTL_QDA_REMOTE_INVOKE with struct drm_qda_invoke_args and
the per-argument descriptor struct drm_qda_fastrpc_invoke_args.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Drop the DMA-BUF fd argument path. Buffer arguments are now identified
by GEM handles only; user space is responsible for importing fds to
GEM handles before invoking (Dmitry Baryshkov)
- Leave argument packing and overlap handling to user space rather than
supporting several buffer-passing formats in the driver
(Dmitry Baryshkov)
---
drivers/accel/qda/Makefile | 1 +
drivers/accel/qda/qda_drv.c | 8 +
drivers/accel/qda/qda_drv.h | 8 +
drivers/accel/qda/qda_fastrpc.c | 434 ++++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_fastrpc.h | 242 ++++++++++++++++++++++
drivers/accel/qda/qda_ioctl.c | 83 ++++++++
drivers/accel/qda/qda_ioctl.h | 1 +
drivers/accel/qda/qda_rpmsg.c | 91 ++++++++-
drivers/accel/qda/qda_rpmsg.h | 26 +++
include/uapi/drm/qda_accel.h | 42 ++++
10 files changed, 934 insertions(+), 2 deletions(-)
diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
index fb092e56d7f3..2d10420cd1ec 100644
--- a/drivers/accel/qda/Makefile
+++ b/drivers/accel/qda/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_ACCEL_QDA) := qda.o
qda-y := \
qda_cb.o \
qda_drv.o \
+ qda_fastrpc.o \
qda_gem.o \
qda_ioctl.o \
qda_memory_dma.o \
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index a68a07d0ae56..f925bbcfa6e9 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -25,6 +25,8 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
qda_file_priv->pid = current->pid;
qda_file_priv->qda_dev = qda_dev_from_drm(dev);
+ qda_file_priv->remote_session_id =
+ atomic_inc_return(&qda_file_priv->qda_dev->remote_session_id_counter);
file->driver_priv = qda_file_priv;
return 0;
@@ -47,6 +49,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 = {
@@ -82,6 +85,7 @@ void qda_deinit_device(struct qda_dev *qdev)
kfree(qdev->iommu_mgr);
qdev->iommu_mgr = NULL;
}
+ xa_destroy(&qdev->ctx_xa);
}
int qda_init_device(struct qda_dev *qdev, int num_cbs)
@@ -90,8 +94,11 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
mutex_init(&qdev->import_lock);
qdev->current_import_file_priv = NULL;
+ atomic_set(&qdev->remote_session_id_counter, 0);
+ xa_init_flags(&qdev->ctx_xa, XA_FLAGS_ALLOC1);
qdev->iommu_mgr = kzalloc_obj(*qdev->iommu_mgr);
if (!qdev->iommu_mgr) {
+ xa_destroy(&qdev->ctx_xa);
mutex_destroy(&qdev->import_lock);
return -ENOMEM;
}
@@ -100,6 +107,7 @@ int qda_init_device(struct qda_dev *qdev, int num_cbs)
drm_err(&qdev->drm_dev, "Failed to initialize memory manager: %d\n", ret);
kfree(qdev->iommu_mgr);
qdev->iommu_mgr = NULL;
+ 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 422e70b971b8..8332108c362f 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -6,10 +6,12 @@
#ifndef __QDA_DRV_H__
#define __QDA_DRV_H__
+#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/list.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>
@@ -28,6 +30,8 @@ struct qda_file_priv {
struct qda_iommu_device *assigned_iommu_dev;
/** @pid: Process ID for tracking */
pid_t pid;
+ /** @remote_session_id: Unique session identifier */
+ u32 remote_session_id;
};
/**
@@ -51,8 +55,12 @@ struct qda_dev {
struct mutex import_lock;
/** @current_import_file_priv: Current file_priv during prime import */
struct drm_file *current_import_file_priv;
+ /** @ctx_xa: XArray for FastRPC context management */
+ struct xarray ctx_xa;
/** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
const char *dsp_name;
+ /** @remote_session_id_counter: Atomic counter for unique session IDs */
+ atomic_t remote_session_id_counter;
};
/**
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
new file mode 100644
index 000000000000..3c0ce5040500
--- /dev/null
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/completion.h>
+#include <linux/dma-buf.h>
+#include <drm/drm_gem.h>
+#include "qda_fastrpc.h"
+#include "qda_drv.h"
+#include "qda_gem.h"
+#include "qda_memory_manager.h"
+
+/*
+ * The driver only accepts GEM handles from user space; userspace imports
+ * DMA-BUF fds with DRM_IOCTL_PRIME_FD_TO_HANDLE before issuing any ioctl that
+ * carries a buffer reference.
+ *
+ * The caller owns the returned reference and must drop it with
+ * drm_gem_object_put().
+ */
+static int get_gem_obj_from_handle(struct qda_fastrpc_invoke_ctx *ctx, u32 handle,
+ struct drm_gem_object **gem_obj)
+{
+ *gem_obj = drm_gem_object_lookup(ctx->file_priv, handle);
+ if (!*gem_obj)
+ return -ENOENT;
+
+ return 0;
+}
+
+static void setup_pages_from_gem_obj(struct qda_gem_obj *qda_gem_obj,
+ struct fastrpc_phy_page *pages)
+{
+ pages->addr = qda_gem_obj->dma_addr;
+ pages->size = qda_gem_obj->size;
+}
+
+static u64 calculate_vma_offset(u64 user_ptr)
+{
+ struct vm_area_struct *vma;
+ u64 vma_offset = 0;
+
+ mmap_read_lock(current->mm);
+ vma = find_vma(current->mm, user_ptr);
+ if (vma)
+ vma_offset = (user_ptr & PAGE_MASK) - vma->vm_start;
+ mmap_read_unlock(current->mm);
+
+ return vma_offset;
+}
+
+static u64 calculate_page_aligned_size(u64 ptr, u64 len)
+{
+ u64 pg_start = (ptr & PAGE_MASK) >> PAGE_SHIFT;
+ u64 pg_end = ((ptr + len - 1) & PAGE_MASK) >> PAGE_SHIFT;
+
+ return (pg_end - pg_start + 1) * PAGE_SIZE;
+}
+
+static struct fastrpc_invoke_buf *fastrpc_invoke_buf_start(union fastrpc_remote_arg *pra, int len)
+{
+ return (struct fastrpc_invoke_buf *)(&pra[len]);
+}
+
+static struct fastrpc_phy_page *fastrpc_phy_page_start(struct fastrpc_invoke_buf *buf, int len)
+{
+ return (struct fastrpc_phy_page *)(&buf[len]);
+}
+
+static size_t fastrpc_get_meta_size(struct qda_fastrpc_invoke_ctx *ctx)
+{
+ return (sizeof(struct fastrpc_remote_buf) +
+ sizeof(struct fastrpc_invoke_buf) +
+ sizeof(struct fastrpc_phy_page)) * ctx->nscalars +
+ sizeof(u64) * FASTRPC_MAX_HANDLELIST +
+ sizeof(u32) * FASTRPC_MAX_CRCLIST;
+}
+
+/**
+ * qda_fastrpc_cleanup_handlelist() - Release DMA handles the DSP no longer needs
+ * @ctx: Invocation context whose response buffer contains the handle list
+ *
+ * The DSP signals handle release by writing GEM handles into the handle list
+ * region of the response buffer. Walk the list and close each handle, which
+ * drops the GEM reference and tears down the IOMMU mapping. This must be called
+ * after dma_rmb() has made the DSP's writes visible.
+ */
+void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx)
+{
+ u64 *handlelist;
+ int i;
+
+ handlelist = (u64 *)(ctx->pages + ctx->nscalars);
+ for (i = 0; i < FASTRPC_MAX_HANDLELIST; i++) {
+ if (!handlelist[i])
+ break;
+ drm_gem_handle_delete(ctx->file_priv, (u32)handlelist[i]);
+ }
+}
+
+/**
+ * qda_fastrpc_context_free() - Release an invocation context
+ * @ref: Reference counter embedded in the context
+ *
+ * Called when the last reference is dropped, which may be either the caller
+ * that issued the invocation or the response callback. The context is removed
+ * from the lookup table here so that a late response can never find a context
+ * that is already being destroyed.
+ *
+ * ctx->req, ctx->rsp, ctx->inbuf and ctx->input_pages point into kernel-owned
+ * GEM mappings and are released along with ctx->gem_objs[].
+ */
+void qda_fastrpc_context_free(struct kref *ref)
+{
+ struct qda_fastrpc_invoke_ctx *ctx;
+ int i;
+
+ ctx = container_of(ref, struct qda_fastrpc_invoke_ctx, refcount);
+
+ xa_erase(&ctx->qdev->ctx_xa, ctx->ctxid >> 4);
+
+ if (ctx->gem_objs) {
+ for (i = 0; i < ctx->nscalars; ++i) {
+ if (ctx->gem_objs[i])
+ drm_gem_object_put(ctx->gem_objs[i]);
+ }
+ kfree(ctx->gem_objs);
+ }
+
+ if (ctx->msg_gem_obj)
+ drm_gem_object_put(&ctx->msg_gem_obj->base);
+
+ kfree(ctx->args);
+ kfree(ctx);
+}
+
+/**
+ * qda_fastrpc_flush_pending() - Fail every outstanding invocation
+ * @qdev: Device whose contexts should be flushed
+ *
+ * Called when the remote processor goes away. The DSP will never answer the
+ * in-flight invocations, so this stands in for the response callback: it wakes
+ * each waiter with an error and drops the reference the send path took for that
+ * callback. The waiter then tears the context down, releasing the GEM objects
+ * it pinned.
+ *
+ * Must run after drm_dev_unplug() has drained qda_rpmsg_cb(), so the real
+ * callback can no longer run and this is the only path completing a context.
+ */
+void qda_fastrpc_flush_pending(struct qda_dev *qdev)
+{
+ struct qda_fastrpc_invoke_ctx *ctx;
+ unsigned long idx;
+
+ xa_for_each(&qdev->ctx_xa, idx, ctx) {
+ ctx->retval = -EPIPE;
+ complete(&ctx->work);
+ kref_put(&ctx->refcount, qda_fastrpc_context_free);
+ }
+}
+
+/**
+ * qda_fastrpc_context_alloc() - Allocate a FastRPC invocation context
+ * @qdev: Device whose lookup table the context is registered in
+ *
+ * Return: Pointer to the new context, or ERR_PTR on failure
+ */
+struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
+{
+ struct qda_fastrpc_invoke_ctx *ctx;
+ int ret;
+ u32 id;
+
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&ctx->node);
+ init_completion(&ctx->work);
+ kref_init(&ctx->refcount);
+ ctx->qdev = qdev;
+ ctx->retval = -1;
+ ctx->pid = current->pid;
+
+ ret = xa_alloc(&qdev->ctx_xa, &id, ctx, xa_limit_32b, GFP_KERNEL);
+ if (ret) {
+ kfree(ctx);
+ return ERR_PTR(ret);
+ }
+ ctx->ctxid = id << 4;
+
+ return ctx;
+}
+
+/*
+ * Resolve the physical address of an in/out buffer argument. The buffer is
+ * backed either by a kernel-owned GEM object already recorded in
+ * ctx->gem_objs[i], or by a user-supplied GEM handle in ctx->args[i].handle.
+ */
+static int process_msg_buffer(struct qda_fastrpc_invoke_ctx *ctx, int i,
+ union fastrpc_remote_arg *rpra,
+ struct fastrpc_phy_page *pages)
+{
+ u64 len = ctx->args[i].length;
+ struct qda_gem_obj *qda_gem_obj;
+
+ rpra[i].buf.pv = ctx->args[i].ptr;
+
+ if (ctx->gem_objs[i]) {
+ u64 unaligned_addr, offset;
+
+ qda_gem_obj = to_qda_gem_obj(ctx->gem_objs[i]);
+ offset = ctx->args[i].ptr - (u64)(uintptr_t)qda_gem_obj->virt;
+
+ unaligned_addr = qda_gem_obj->dma_addr + offset;
+ pages[i].addr = unaligned_addr & PAGE_MASK;
+ pages[i].size = calculate_page_aligned_size(unaligned_addr, len);
+ } else if (ctx->args[i].handle) {
+ struct drm_gem_object *gem_obj;
+ int err;
+
+ err = get_gem_obj_from_handle(ctx, ctx->args[i].handle, &gem_obj);
+ if (err)
+ return err;
+
+ ctx->gem_objs[i] = gem_obj;
+ qda_gem_obj = to_qda_gem_obj(gem_obj);
+
+ pages[i].addr = qda_gem_obj->dma_addr +
+ calculate_vma_offset(ctx->args[i].ptr);
+ pages[i].size = calculate_page_aligned_size(ctx->args[i].ptr, len);
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Resolve a DMA-handle argument. The kernel needs the physical page
+ * descriptor, while the DSP identifies the buffer by its GEM handle.
+ */
+static int process_dma_handle(struct qda_fastrpc_invoke_ctx *ctx, int i,
+ union fastrpc_remote_arg *rpra,
+ struct fastrpc_phy_page *pages)
+{
+ struct drm_gem_object *gem_obj;
+ int err;
+
+ if (!ctx->args[i].handle) {
+ rpra[i].buf.pv = ctx->args[i].ptr;
+ rpra[i].buf.len = ctx->args[i].length;
+ return 0;
+ }
+
+ err = get_gem_obj_from_handle(ctx, ctx->args[i].handle, &gem_obj);
+ if (err)
+ return err;
+
+ ctx->gem_objs[i] = gem_obj;
+ setup_pages_from_gem_obj(to_qda_gem_obj(gem_obj), &pages[i]);
+
+ /* The DSP-facing offset and length fields are 32-bit by wire format */
+ rpra[i].dma.handle = ctx->args[i].handle;
+ rpra[i].dma.len = (u32)ctx->args[i].length;
+ rpra[i].dma.offset = (u32)ctx->args[i].ptr;
+
+ return 0;
+}
+
+/**
+ * qda_fastrpc_get_header_size() - Compute the FastRPC message header size
+ * @ctx: FastRPC invocation context
+ *
+ * Also caches the intermediate metadata and packet sizes in @ctx for the
+ * subsequent pack step.
+ *
+ * Return: Page-aligned size of the message payload buffer in bytes
+ */
+size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx)
+{
+ ctx->inbufs = REMOTE_SCALARS_INBUFS(ctx->sc);
+ ctx->metalen = fastrpc_get_meta_size(ctx);
+ ctx->pkt_size = ALIGN(ctx->metalen, FASTRPC_ALIGN);
+ ctx->aligned_pkt_size = PAGE_ALIGN(ctx->pkt_size);
+
+ return ctx->aligned_pkt_size;
+}
+
+static int qda_fastrpc_get_args(struct qda_fastrpc_invoke_ctx *ctx)
+{
+ struct fastrpc_invoke_buf *list;
+ struct fastrpc_phy_page *pages;
+ union fastrpc_remote_arg *rpra;
+ int i, err;
+
+ ctx->msg->buf = ctx->msg_gem_obj->virt;
+ ctx->msg->phys = ctx->msg_gem_obj->dma_addr;
+ memset(ctx->msg->buf, 0, ctx->aligned_pkt_size);
+
+ rpra = ctx->msg->buf;
+ list = fastrpc_invoke_buf_start(rpra, ctx->nscalars);
+ pages = fastrpc_phy_page_start(list, ctx->nscalars);
+ ctx->rpra = rpra;
+ ctx->list = list;
+ ctx->pages = pages;
+
+ for (i = 0; i < ctx->nbufs; ++i) {
+ rpra[i].buf.pv = 0;
+ rpra[i].buf.len = ctx->args[i].length;
+ list[i].num = ctx->args[i].length ? 1 : 0;
+ list[i].pgidx = i;
+
+ if (!ctx->args[i].length)
+ continue;
+
+ err = process_msg_buffer(ctx, i, rpra, pages);
+ if (err)
+ return err;
+ }
+
+ for (i = ctx->nbufs; i < ctx->nscalars; ++i) {
+ list[i].num = ctx->args[i].length ? 1 : 0;
+ list[i].pgidx = i;
+
+ err = process_dma_handle(ctx, i, rpra, pages);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * qda_fastrpc_invoke_pack() - Pack an invocation context into a QDA message
+ * @ctx: FastRPC invocation context
+ * @msg: QDA message to fill in
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg)
+{
+ int err;
+
+ ctx->msg = msg;
+
+ err = qda_fastrpc_get_args(ctx);
+ if (err)
+ return err;
+
+ /* Ensure the payload is visible to the DSP before the doorbell */
+ dma_wmb();
+
+ if (ctx->handle == FASTRPC_INIT_HANDLE)
+ msg->fastrpc.remote_session_id = 0;
+ else
+ msg->fastrpc.remote_session_id = ctx->remote_session_id;
+
+ msg->fastrpc.tid = ctx->pid;
+ msg->fastrpc.ctx = ctx->ctxid | ctx->pd;
+ msg->fastrpc.handle = ctx->handle;
+ msg->fastrpc.sc = ctx->sc;
+ msg->fastrpc.addr = msg->phys;
+ msg->fastrpc.size = roundup(ctx->pkt_size, PAGE_SIZE);
+ msg->fastrpc_ctx = ctx;
+ msg->file_priv = ctx->file_priv;
+
+ return 0;
+}
+
+/*
+ * INVOKE_DYNAMIC: the argument descriptors are supplied by user space, which
+ * is also responsible for having imported every buffer to a GEM handle.
+ */
+static int qda_fastrpc_prepare_args_invoke(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ struct drm_qda_invoke_args *invoke_args = argp;
+ struct drm_qda_fastrpc_invoke_args *args;
+ u32 nscalars;
+
+ ctx->handle = invoke_args->handle;
+ ctx->sc = invoke_args->sc;
+
+ nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+ if (!nscalars)
+ return 0;
+
+ args = kzalloc_objs(*args, nscalars);
+ if (!args)
+ return -ENOMEM;
+
+ if (copy_from_user(args, u64_to_user_ptr(invoke_args->args),
+ nscalars * sizeof(*args))) {
+ kfree(args);
+ return -EFAULT;
+ }
+
+ ctx->args = args;
+
+ return 0;
+}
+
+/**
+ * qda_fastrpc_prepare_args() - Prepare arguments for a FastRPC invocation
+ * @ctx: FastRPC invocation context
+ * @argp: Kernel-side ioctl argument buffer owned by the DRM core
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ int err;
+
+ switch (ctx->type) {
+ case FASTRPC_RMID_INVOKE_DYNAMIC:
+ err = qda_fastrpc_prepare_args_invoke(ctx, argp);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err)
+ return err;
+
+ ctx->nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+ ctx->nbufs = REMOTE_SCALARS_INBUFS(ctx->sc) + REMOTE_SCALARS_OUTBUFS(ctx->sc);
+
+ if (ctx->nscalars && !ctx->gem_objs) {
+ ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, ctx->nscalars);
+ if (!ctx->gem_objs)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/drivers/accel/qda/qda_fastrpc.h b/drivers/accel/qda/qda_fastrpc.h
new file mode 100644
index 000000000000..a25818923a6e
--- /dev/null
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_FASTRPC_H__
+#define __QDA_FASTRPC_H__
+
+#include <linux/completion.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/qda_accel.h>
+
+struct qda_dev;
+struct qda_gem_obj;
+struct qda_msg;
+
+/*
+ * FastRPC scalar extraction macros
+ *
+ * These macros extract different fields from the scalar value that describes
+ * the arguments passed in a FastRPC invocation.
+ */
+#define REMOTE_SCALARS_INBUFS(sc) (((sc) >> 16) & 0x0ff)
+#define REMOTE_SCALARS_OUTBUFS(sc) (((sc) >> 8) & 0x0ff)
+#define REMOTE_SCALARS_INHANDLES(sc) (((sc) >> 4) & 0x0f)
+#define REMOTE_SCALARS_OUTHANDLES(sc) ((sc) & 0x0f)
+#define REMOTE_SCALARS_LENGTH(sc) (REMOTE_SCALARS_INBUFS(sc) + \
+ REMOTE_SCALARS_OUTBUFS(sc) + \
+ REMOTE_SCALARS_INHANDLES(sc) + \
+ REMOTE_SCALARS_OUTHANDLES(sc))
+
+/* FastRPC configuration constants */
+#define FASTRPC_ALIGN 128 /* Alignment requirement */
+#define FASTRPC_MAX_HANDLELIST 16 /* Maximum handles the DSP can release */
+#define FASTRPC_MAX_CRCLIST 64 /* Maximum CRC list entries */
+
+/*
+ * FastRPC scalar construction macros
+ *
+ * These macros build the scalar value that describes the arguments
+ * for a FastRPC invocation.
+ */
+#define FASTRPC_BUILD_SCALARS(attr, method, in, out, oin, oout) \
+ (((attr & 0x07) << 29) | \
+ ((method & 0x1f) << 24) | \
+ ((in & 0xff) << 16) | \
+ ((out & 0xff) << 8) | \
+ ((oin & 0x0f) << 4) | \
+ (oout & 0x0f))
+
+#define FASTRPC_SCALARS(method, in, out) \
+ FASTRPC_BUILD_SCALARS(0, method, in, out, 0, 0)
+
+/**
+ * struct fastrpc_remote_dmahandle - Remote DMA handle descriptor
+ */
+struct fastrpc_remote_dmahandle {
+ /** @handle: Handle of the buffer */
+ s32 handle;
+ /** @offset: Byte offset within the buffer object */
+ u32 offset;
+ /** @len: Length of the region in bytes */
+ u32 len;
+};
+
+/**
+ * struct fastrpc_remote_buf - Remote buffer descriptor
+ */
+struct fastrpc_remote_buf {
+ /** @pv: Buffer pointer (user virtual address) */
+ u64 pv;
+ /** @len: Length of the buffer in bytes */
+ u64 len;
+};
+
+/**
+ * union fastrpc_remote_arg - Remote argument (buffer or DMA handle)
+ */
+union fastrpc_remote_arg {
+ /** @buf: Inline buffer descriptor */
+ struct fastrpc_remote_buf buf;
+ /** @dma: DMA-BUF handle descriptor */
+ struct fastrpc_remote_dmahandle dma;
+};
+
+/**
+ * struct fastrpc_phy_page - Physical page descriptor
+ */
+struct fastrpc_phy_page {
+ /** @addr: Physical (IOMMU) address of the page */
+ u64 addr;
+ /** @size: Size of the contiguous region in bytes */
+ u64 size;
+};
+
+/**
+ * struct fastrpc_invoke_buf - Invoke buffer descriptor
+ */
+struct fastrpc_invoke_buf {
+ /** @num: Number of contiguous physical regions */
+ u32 num;
+ /** @pgidx: Index into the physical page array */
+ u32 pgidx;
+};
+
+/**
+ * struct fastrpc_msg - FastRPC wire message for remote invocations
+ *
+ * Sent to the remote processor via RPMsg. This is the exact layout
+ * the DSP expects; do not reorder or add fields without DSP firmware
+ * coordination.
+ */
+struct 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;
+};
+
+/**
+ * struct qda_fastrpc_invoke_ctx - Remote procedure call invocation context
+ *
+ * Maintains all state for a single remote procedure call, including buffer
+ * management, synchronisation, and result handling.
+ */
+struct qda_fastrpc_invoke_ctx {
+ /** @node: List node for linking contexts in a queue */
+ struct list_head node;
+ /** @qdev: Device owning the XArray this context is registered in */
+ struct qda_dev *qdev;
+ /** @ctxid: Unique context identifier (XArray key shifted left by 4) */
+ u64 ctxid;
+ /** @inbufs: Number of input buffers */
+ int inbufs;
+ /** @nscalars: Total number of scalar arguments */
+ int nscalars;
+ /** @nbufs: Total number of buffer arguments (inbufs + outbufs) */
+ int nbufs;
+ /** @pid: Process ID of the calling process */
+ int pid;
+ /** @retval: Status code reported by the DSP for this invocation */
+ int retval;
+ /** @remote_session_id: Session identifier on the remote processor */
+ int remote_session_id;
+ /** @pd: Protection domain identifier encoded into the context ID */
+ int pd;
+ /** @type: Invocation type (e.g. FASTRPC_RMID_INVOKE_DYNAMIC) */
+ u32 type;
+ /** @sc: Scalars value encoding in/out buffer counts */
+ u32 sc;
+ /** @handle: Handle of the remote method being invoked */
+ u32 handle;
+ /** @metalen: Length of the FastRPC metadata header in bytes */
+ size_t metalen;
+ /** @pkt_size: Total payload size in bytes */
+ u64 pkt_size;
+ /** @aligned_pkt_size: Page-aligned payload size for GEM allocation */
+ u64 aligned_pkt_size;
+ /** @list: Array of invoke buffer descriptors */
+ struct fastrpc_invoke_buf *list;
+ /** @pages: Array of physical page descriptors for all arguments */
+ struct fastrpc_phy_page *pages;
+ /** @input_pages: Array of physical page descriptors for input buffers */
+ struct fastrpc_phy_page *input_pages;
+ /** @work: Completion used to synchronise with the DSP response */
+ struct completion work;
+ /** @msg: Pointer to the QDA message structure for this invocation */
+ struct qda_msg *msg;
+ /** @rpra: Array of remote procedure arguments */
+ union fastrpc_remote_arg *rpra;
+ /** @gem_objs: Array of GEM objects imported for argument buffers */
+ struct drm_gem_object **gem_objs;
+ /** @args: Invoke argument descriptors */
+ struct drm_qda_fastrpc_invoke_args *args;
+ /** @refcount: Reference counter for context lifetime management */
+ struct kref refcount;
+ /** @msg_gem_obj: GEM object backing the message payload buffer */
+ struct qda_gem_obj *msg_gem_obj;
+ /** @file_priv: DRM file private data */
+ struct drm_file *file_priv;
+ /**
+ * @req: Request buffer for the internal init/map/unmap calls. Points
+ * into a kernel-owned GEM mapping tracked by @gem_objs, so it must
+ * never be freed directly.
+ */
+ void *req;
+ /** @rsp: Response buffer, same lifetime rules as @req */
+ void *rsp;
+ /** @inbuf: Process-create input buffer, same lifetime rules as @req */
+ void *inbuf;
+};
+
+/**
+ * struct qda_msg - FastRPC message with kernel-internal bookkeeping
+ */
+struct qda_msg {
+ /**
+ * @fastrpc: Wire-format message sent to the DSP via RPMsg.
+ * Must be the first member.
+ */
+ struct fastrpc_msg fastrpc;
+ /** @buf: Kernel virtual address of the payload buffer */
+ void *buf;
+ /** @phys: Physical/DMA address of the payload buffer */
+ u64 phys;
+ /** @ret: Return value from the remote processor */
+ int ret;
+ /** @fastrpc_ctx: Back-pointer to the owning invocation context */
+ struct qda_fastrpc_invoke_ctx *fastrpc_ctx;
+ /** @file_priv: DRM file private data for GEM object lookup */
+ struct drm_file *file_priv;
+};
+
+/* Remote Method ID table - identifies initialization and control operations */
+#define FASTRPC_RMID_INVOKE_DYNAMIC 0xFFFFFFFFU /* Dynamic method invocation */
+
+/* Common handle for initialization operations */
+#define FASTRPC_INIT_HANDLE 0x1
+
+void qda_fastrpc_context_free(struct kref *ref);
+void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx);
+void qda_fastrpc_flush_pending(struct qda_dev *qdev);
+struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev);
+int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp);
+size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx);
+int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg);
+
+#endif /* __QDA_FASTRPC_H__ */
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index f65325c80a12..b7ee4899ba74 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#include <drm/drm_ioctl.h>
+#include <drm/drm_print.h>
#include <drm/qda_accel.h>
#include "qda_drv.h"
+#include "qda_fastrpc.h"
#include "qda_gem.h"
#include "qda_ioctl.h"
+#include "qda_rpmsg.h"
/**
* qda_ioctl_query() - Query DSP device information
@@ -83,3 +86,83 @@ int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void *data, struct drm_fil
return drm_gem_dumb_map_offset(file_priv, dev, args->handle, &args->offset);
}
+
+static int qda_fastrpc_invoke(u32 type, struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct qda_file_priv *qda_file_priv = file_priv->driver_priv;
+ struct qda_dev *qdev = qda_file_priv->qda_dev;
+ struct qda_fastrpc_invoke_ctx *ctx;
+ struct drm_gem_object *gem_obj;
+ struct qda_msg msg;
+ size_t hdr_size;
+ int err;
+
+ ctx = qda_fastrpc_context_alloc(qdev);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ctx->type = type;
+ ctx->file_priv = file_priv;
+ ctx->remote_session_id = qda_file_priv->remote_session_id;
+
+ err = qda_fastrpc_prepare_args(ctx, data);
+ if (err)
+ goto err_context_free;
+
+ hdr_size = qda_fastrpc_get_header_size(ctx);
+
+ gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr, hdr_size, file_priv);
+ if (IS_ERR(gem_obj)) {
+ err = PTR_ERR(gem_obj);
+ goto err_context_free;
+ }
+
+ ctx->msg_gem_obj = to_qda_gem_obj(gem_obj);
+
+ err = qda_fastrpc_invoke_pack(ctx, &msg);
+ if (err)
+ goto err_context_free;
+
+ err = qda_rpmsg_send_msg(qdev, &msg);
+ if (err)
+ goto err_context_free;
+
+ err = qda_rpmsg_wait_for_rsp(ctx);
+ if (err)
+ goto err_context_free;
+
+ /* Ensure the results written by the DSP are visible before reading */
+ dma_rmb();
+
+ err = ctx->retval;
+ if (err) {
+ drm_dbg_driver(dev, "DSP returned status 0x%x for type %u\n",
+ ctx->retval, type);
+ goto err_context_free;
+ }
+
+ qda_fastrpc_cleanup_handlelist(ctx);
+
+ kref_put(&ctx->refcount, qda_fastrpc_context_free);
+
+ return 0;
+
+err_context_free:
+ kref_put(&ctx->refcount, qda_fastrpc_context_free);
+
+ return err;
+}
+
+/**
+ * qda_ioctl_invoke() - Perform a dynamic FastRPC method invocation
+ * @dev: DRM device structure
+ * @data: User-space data (struct qda_invoke_args)
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_priv)
+{
+ return qda_fastrpc_invoke(FASTRPC_RMID_INVOKE_DYNAMIC, dev, data, file_priv);
+}
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index d1cbbfb6d965..3bb9cfd98370 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -11,5 +11,6 @@
int qda_ioctl_query(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qda_ioctl_gem_mmap_offset(struct drm_device *dev, void *data, struct drm_file *file_priv);
+int qda_ioctl_invoke(struct drm_device *dev, void *data, struct drm_file *file_priv);
#endif /* __QDA_IOCTL_H__ */
diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
index 64bf503106d9..7a5e0d3e20ca 100644
--- a/drivers/accel/qda/qda_rpmsg.c
+++ b/drivers/accel/qda/qda_rpmsg.c
@@ -1,12 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+#include <linux/completion.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/rpmsg.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
#include <drm/drm_print.h>
#include "qda_cb.h"
#include "qda_drv.h"
+#include "qda_fastrpc.h"
+#include "qda_rpmsg.h"
static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
{
@@ -23,11 +28,92 @@ static struct qda_dev *qda_rpmsg_alloc_and_init_qdev(struct rpmsg_device *rpdev)
return qdev;
}
+/**
+ * qda_rpmsg_send_msg() - Send a packed invocation to the remote processor
+ * @qdev: QDA device structure
+ * @msg: Packed message to send
+ *
+ * Takes a reference on the invocation context on behalf of the response
+ * callback, which keeps the context alive until the DSP has replied.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_rpmsg_send_msg(struct qda_dev *qdev, struct qda_msg *msg)
+{
+ struct qda_fastrpc_invoke_ctx *ctx = msg->fastrpc_ctx;
+ int ret, idx;
+
+ if (!drm_dev_enter(&qdev->drm_dev, &idx))
+ return -ENODEV;
+
+ if (!qdev->rpdev) {
+ ret = -ENODEV;
+ goto out_exit;
+ }
+
+ kref_get(&ctx->refcount);
+
+ ret = rpmsg_send(qdev->rpdev->ept, &msg->fastrpc, sizeof(msg->fastrpc));
+ if (ret) {
+ drm_err_ratelimited(&qdev->drm_dev, "Failed to send rpmsg: %d\n", ret);
+ kref_put(&ctx->refcount, qda_fastrpc_context_free);
+ }
+
+out_exit:
+ drm_dev_exit(idx);
+
+ return ret;
+}
+
+/**
+ * qda_rpmsg_wait_for_rsp() - Wait for the DSP to answer an invocation
+ * @ctx: FastRPC invocation context
+ *
+ * Return: 0 once the response arrived, or -ERESTARTSYS if a signal
+ * interrupted the wait
+ */
+int qda_rpmsg_wait_for_rsp(struct qda_fastrpc_invoke_ctx *ctx)
+{
+ return wait_for_completion_interruptible(&ctx->work);
+}
+
static int qda_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
- /* Placeholder: responses will be dispatched here */
- return 0;
+ struct qda_dev *qdev = dev_get_drvdata(&rpdev->dev);
+ struct qda_invoke_rsp *resp_msg = data;
+ struct qda_fastrpc_invoke_ctx *ctx;
+ int ret = 0, idx;
+
+ if (!qdev)
+ return -ENODEV;
+
+ if (!drm_dev_enter(&qdev->drm_dev, &idx))
+ return -ENODEV;
+
+ if (len < sizeof(*resp_msg)) {
+ ret = -EINVAL;
+ goto out_exit;
+ }
+
+ ctx = xa_load(&qdev->ctx_xa, resp_msg->ctx >> 4);
+ if (!ctx) {
+ drm_dbg_driver(&qdev->drm_dev, "No context matches response 0x%llx\n",
+ resp_msg->ctx);
+ ret = -ENOENT;
+ goto out_exit;
+ }
+
+ ctx->retval = resp_msg->retval;
+ complete(&ctx->work);
+
+ /* Release the reference taken by qda_rpmsg_send_msg() */
+ kref_put(&ctx->refcount, qda_fastrpc_context_free);
+
+out_exit:
+ drm_dev_exit(idx);
+
+ return ret;
}
static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
@@ -41,6 +127,7 @@ static void qda_rpmsg_remove(struct rpmsg_device *rpdev)
*/
drm_dev_unplug(&qdev->drm_dev);
qdev->rpdev = NULL;
+ qda_fastrpc_flush_pending(qdev);
qda_cb_unpopulate(qdev);
qda_deinit_device(qdev);
}
diff --git a/drivers/accel/qda/qda_rpmsg.h b/drivers/accel/qda/qda_rpmsg.h
new file mode 100644
index 000000000000..a70f4a80808d
--- /dev/null
+++ b/drivers/accel/qda/qda_rpmsg.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QDA_RPMSG_H__
+#define __QDA_RPMSG_H__
+
+#include "qda_drv.h"
+#include "qda_fastrpc.h"
+
+/**
+ * struct qda_invoke_rsp - Response structure for FastRPC invocations
+ */
+struct qda_invoke_rsp {
+ /** @ctx: Invoke caller context for matching request/response */
+ u64 ctx;
+ /** @retval: Return value from the remote invocation */
+ int retval;
+};
+
+/* RPMsg transport layer functions */
+int qda_rpmsg_send_msg(struct qda_dev *qdev, struct qda_msg *msg);
+int qda_rpmsg_wait_for_rsp(struct qda_fastrpc_invoke_ctx *ctx);
+
+#endif /* __QDA_RPMSG_H__ */
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 1d699f00e7be..5cf0fec1d91c 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -21,6 +21,8 @@ extern "C" {
#define DRM_QDA_QUERY 0x00
#define DRM_QDA_GEM_CREATE 0x01
#define DRM_QDA_GEM_MMAP_OFFSET 0x02
+/* Command numbers 0x03-0x06 reserved for INIT_ATTACH, INIT_CREATE, MAP, MUNMAP */
+#define DRM_QDA_REMOTE_INVOKE 0x07
/*
* QDA IOCTL definitions
@@ -35,6 +37,8 @@ extern "C" {
struct drm_qda_gem_create)
#define DRM_IOCTL_QDA_GEM_MMAP_OFFSET DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_GEM_MMAP_OFFSET, \
struct drm_qda_gem_mmap_offset)
+#define DRM_IOCTL_QDA_REMOTE_INVOKE DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_INVOKE, \
+ struct drm_qda_invoke_args)
/* Query type definitions for drm_qda_query */
#define QDA_QUERY_DSP_NAME 1
@@ -84,6 +88,44 @@ struct drm_qda_gem_mmap_offset {
__u32 pad;
};
+/**
+ * struct drm_qda_fastrpc_invoke_args - FastRPC invocation argument descriptor
+ * @ptr: Pointer to argument data (user virtual address)
+ * @length: Length of the argument data in bytes
+ * @handle: GEM handle for buffer arguments; 0 for scalar arguments
+ * @attr: Argument attributes and flags
+ *
+ * This structure describes a single argument passed to a FastRPC invocation.
+ * Arguments can be either scalar values or buffer references (via GEM handle).
+ * Userspace must import any DMA-BUF fd to a GEM handle before populating
+ * this field; the driver never accepts DMA-BUF fds directly.
+ */
+struct drm_qda_fastrpc_invoke_args {
+ __u64 ptr;
+ __u64 length;
+ __u32 handle;
+ __u32 attr;
+};
+
+/**
+ * struct drm_qda_invoke_args - Dynamic FastRPC invocation parameters
+ * @handle: Remote handle to invoke on the DSP
+ * @sc: FastRPC scalars value encoding the number of in/out buffers
+ * @args: User-space pointer to array of drm_qda_fastrpc_invoke_args descriptors;
+ * the handle field in each entry must be a GEM handle (or 0 for
+ * inline scalar buffers). Userspace must import DMA-BUF fds to GEM
+ * handles before passing them here.
+ *
+ * This structure is used with DRM_IOCTL_QDA_REMOTE_INVOKE to perform a
+ * dynamic remote procedure call on the DSP. The args pointer must reference
+ * an array of REMOTE_SCALARS_LENGTH(sc) drm_qda_fastrpc_invoke_args entries.
+ */
+struct drm_qda_invoke_args {
+ __u32 handle;
+ __u32 sc;
+ __u64 args;
+};
+
#if defined(__cplusplus)
}
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v2 13/15] accel/qda: Add DSP process creation and release
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (11 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 12/15] accel/qda: Add FastRPC invocation support Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 14/15] accel/qda: Add remote memory mapping to DSP address space Ekansh Gupta
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Implement the REMOTE_SESSION_CREATE and INIT_RELEASE FastRPC operations,
which establish and tear down a user process on the DSP.
DRM_IOCTL_QDA_REMOTE_SESSION_CREATE (drm_qda_init_create)
Creates a new process on the DSP by sending an INIT_CREATE message via
the FastRPC INIT_HANDLE. The caller provides the ELF image as a GEM
handle along with optional process attributes. A GEM buffer is
allocated per session to hold the DSP process image; it is cached in
qda_file_priv and reused for the lifetime of the session.
If attrs is non-zero the INIT_CREATE_ATTR message is used instead of
INIT_CREATE. Both create the protection domain and load the process
image; INIT_CREATE_ATTR additionally carries the process attributes
and signature length, which the DSP applies to the domain it creates.
Issuing INIT_CREATE again on the same file without an intervening
INIT_RELEASE replaces the process image, so the initialisation memory
cached for the previous attempt is released first.
INIT_RELEASE
Tears the DSP process down. It is issued from qda_release_dsp_process()
on postclose, guarded by drm_dev_enter() so the message is only sent
while the device is still accessible. Once the DSP has torn down the
protection domain the initialisation memory is released.
The initialisation memory is referenced both by the invocation context
and by the file private data that caches it across invocations, so each
takes a reference of its own and drops it independently.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
Changes in v2:
- Explain how INIT_CREATE_ATTR differs from INIT_CREATE
(Dmitry Baryshkov)
- Take a separate reference on the process initialisation memory for the
file private data and the invocation context, so it is no longer
leaked when process creation fails
- Document why a second INIT_CREATE releases the previous initialisation
memory
---
drivers/accel/qda/qda_drv.c | 12 +++
drivers/accel/qda/qda_drv.h | 5 +
drivers/accel/qda/qda_fastrpc.c | 213 ++++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_fastrpc.h | 35 +++++++
drivers/accel/qda/qda_ioctl.c | 77 +++++++++++++++
drivers/accel/qda/qda_ioctl.h | 1 +
include/uapi/drm/qda_accel.h | 31 +++++-
7 files changed, 373 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index f925bbcfa6e9..4c3a37cbf49e 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_gem.h"
#include "qda_ioctl.h"
#include "qda_prime.h"
@@ -35,6 +36,16 @@ static int qda_open(struct drm_device *dev, struct drm_file *file)
static void qda_postclose(struct drm_device *dev, struct drm_file *file)
{
struct qda_file_priv *qda_file_priv = file->driver_priv;
+ int idx;
+
+ /* Only send the DSP release message while the device is accessible */
+ if (drm_dev_enter(dev, &idx)) {
+ qda_release_dsp_process(qda_file_priv->qda_dev, file);
+ drm_dev_exit(idx);
+ }
+
+ if (qda_file_priv->init_mem_gem_obj)
+ drm_gem_object_put(&qda_file_priv->init_mem_gem_obj->base);
if (qda_file_priv->assigned_iommu_dev)
qda_memory_manager_release_device(qda_file_priv->assigned_iommu_dev);
@@ -49,6 +60,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_SESSION_CREATE, qda_ioctl_init_create, 0),
DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
};
diff --git a/drivers/accel/qda/qda_drv.h b/drivers/accel/qda/qda_drv.h
index 8332108c362f..4337894ff22d 100644
--- a/drivers/accel/qda/qda_drv.h
+++ b/drivers/accel/qda/qda_drv.h
@@ -28,6 +28,8 @@ struct qda_file_priv {
struct qda_dev *qda_dev;
/** @assigned_iommu_dev: IOMMU device assigned to this process */
struct qda_iommu_device *assigned_iommu_dev;
+ /** @init_mem_gem_obj: GEM object for PD initialization memory */
+ struct qda_gem_obj *init_mem_gem_obj;
/** @pid: Process ID for tracking */
pid_t pid;
/** @remote_session_id: Unique session identifier */
@@ -82,4 +84,7 @@ 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);
+/* DSP process / protection domain management */
+int qda_release_dsp_process(struct qda_dev *qdev, struct drm_file *file_priv);
+
#endif /* __QDA_DRV_H__ */
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 3c0ce5040500..2c72f86130aa 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -130,6 +130,9 @@ void qda_fastrpc_context_free(struct kref *ref)
if (ctx->msg_gem_obj)
drm_gem_object_put(&ctx->msg_gem_obj->base);
+ if (ctx->init_mem_gem_obj)
+ drm_gem_object_put(&ctx->init_mem_gem_obj->base);
+
kfree(ctx->args);
kfree(ctx);
}
@@ -192,6 +195,45 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev)
return ctx;
}
+/*
+ * Allocate a single DMA-coherent GEM object holding all kernel-internal
+ * buffers of one invocation, so the total DMA footprint is
+ * PAGE_ALIGN(sum of sizes) - typically a single page.
+ *
+ * The caller wires each argument slot with assign_kernel_gem_arg(), which
+ * takes a reference per slot, then drops the initial reference returned here.
+ */
+static struct qda_gem_obj *alloc_kernel_gem(struct qda_fastrpc_invoke_ctx *ctx,
+ size_t total_size)
+{
+ struct qda_file_priv *qda_priv = ctx->file_priv->driver_priv;
+ struct qda_dev *qdev = qda_priv->qda_dev;
+ struct qda_gem_obj *qda_gem_obj;
+ struct drm_gem_object *gem_obj;
+
+ gem_obj = qda_gem_create_object(&qdev->drm_dev, qdev->iommu_mgr,
+ total_size, ctx->file_priv);
+ if (IS_ERR(gem_obj))
+ return ERR_CAST(gem_obj);
+
+ qda_gem_obj = to_qda_gem_obj(gem_obj);
+ /* Zero-initialise so OUT slots start clean */
+ memset(qda_gem_obj->virt, 0, qda_gem_obj->size);
+
+ return qda_gem_obj;
+}
+
+static void assign_kernel_gem_arg(struct drm_qda_fastrpc_invoke_args *args,
+ struct drm_gem_object **gem_objs, int idx,
+ struct qda_gem_obj *kernel_gem, void *ptr, size_t len)
+{
+ args[idx].ptr = (u64)(uintptr_t)ptr;
+ args[idx].length = len;
+ args[idx].handle = 0;
+ drm_gem_object_get(&kernel_gem->base);
+ gem_objs[idx] = &kernel_gem->base;
+}
+
/*
* Resolve the physical address of an in/out buffer argument. The buffer is
* backed either by a kernel-owned GEM object already recorded in
@@ -368,6 +410,169 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
return 0;
}
+/*
+ * 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 drm_qda_fastrpc_invoke_args *args;
+ struct qda_gem_obj *kernel_gem;
+ u32 nscalars;
+ int err;
+
+ ctx->sc = FASTRPC_SCALARS(FASTRPC_RMID_INIT_RELEASE, 1, 0);
+ ctx->handle = FASTRPC_INIT_HANDLE;
+ nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+
+ args = kzalloc_objs(*args, nscalars);
+ if (!args)
+ return -ENOMEM;
+
+ ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
+ if (!ctx->gem_objs) {
+ err = -ENOMEM;
+ goto err_free_args;
+ }
+
+ kernel_gem = alloc_kernel_gem(ctx, sizeof(ctx->remote_session_id));
+ if (IS_ERR(kernel_gem)) {
+ err = PTR_ERR(kernel_gem);
+ goto err_free_gem_objs;
+ }
+
+ memcpy(kernel_gem->virt, &ctx->remote_session_id,
+ sizeof(ctx->remote_session_id));
+
+ assign_kernel_gem_arg(args, ctx->gem_objs, 0, kernel_gem, kernel_gem->virt,
+ sizeof(ctx->remote_session_id));
+ drm_gem_object_put(&kernel_gem->base);
+
+ ctx->args = args;
+
+ return 0;
+
+err_free_gem_objs:
+ kfree(ctx->gem_objs);
+ ctx->gem_objs = NULL;
+err_free_args:
+ kfree(args);
+
+ return err;
+}
+
+/*
+ * INIT_CREATE / INIT_CREATE_ATTR packed layout:
+ * [0] inbuf sizeof(fastrpc_create_process_inbuf) IN
+ * [1] comm TASK_COMM_LEN IN
+ * [2] ELF user-supplied GEM handle, not packed IN
+ * [3] pages sizeof(fastrpc_phy_page) IN
+ * [4] attrs sizeof(u32) IN
+ * [5] siglen sizeof(u32) IN
+ */
+static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ struct drm_qda_init_create *init = argp;
+ struct fastrpc_create_process_inbuf *inbuf;
+ struct drm_qda_fastrpc_invoke_args *args;
+ struct qda_gem_obj *kernel_gem;
+ u32 nscalars;
+ int err;
+ u8 *gv;
+
+ 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 off_attrs = off_pages + sizeof(struct fastrpc_phy_page);
+ const size_t off_siglen = off_attrs + sizeof(u32);
+ const size_t total_size = off_siglen + sizeof(u32);
+
+ if (init->filelen > FASTRPC_INIT_FILELEN_MAX)
+ return -EINVAL;
+
+ /* Reject an unusable ELF handle before involving the DSP */
+ if (init->filelen && init->filehandle) {
+ struct drm_gem_object *file_gem_obj;
+
+ err = get_gem_obj_from_handle(ctx, init->filehandle, &file_gem_obj);
+ if (err)
+ return err;
+ drm_gem_object_put(file_gem_obj);
+ }
+
+ ctx->sc = init->attrs ? FASTRPC_SCALARS(FASTRPC_RMID_INIT_CREATE_ATTR, 4, 0)
+ : FASTRPC_SCALARS(FASTRPC_RMID_INIT_CREATE, 4, 0);
+ ctx->handle = FASTRPC_INIT_HANDLE;
+ nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+
+ args = kzalloc_objs(*args, FASTRPC_CREATE_PROCESS_NARGS);
+ if (!args)
+ return -ENOMEM;
+
+ ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
+ if (!ctx->gem_objs) {
+ err = -ENOMEM;
+ goto err_free_args;
+ }
+
+ kernel_gem = alloc_kernel_gem(ctx, total_size);
+ if (IS_ERR(kernel_gem)) {
+ err = PTR_ERR(kernel_gem);
+ goto err_free_gem_objs;
+ }
+ gv = kernel_gem->virt;
+
+ inbuf = (struct fastrpc_create_process_inbuf *)(gv + off_inbuf);
+ inbuf->remote_session_id = ctx->remote_session_id;
+ inbuf->namelen = strlen(current->comm) + 1;
+ inbuf->filelen = init->filelen;
+ inbuf->pageslen = 1;
+ inbuf->attrs = init->attrs;
+ inbuf->siglen = init->siglen;
+
+ memcpy(gv + off_comm, current->comm, inbuf->namelen);
+
+ ctx->input_pages = (struct fastrpc_phy_page *)(gv + off_pages);
+ setup_pages_from_gem_obj(ctx->init_mem_gem_obj, ctx->input_pages);
+
+ *(u32 *)(gv + off_attrs) = inbuf->attrs;
+ *(u32 *)(gv + off_siglen) = inbuf->siglen;
+
+ /*
+ * ctx->sc encodes 4 inbufs, so gem_objs[] only has 4 slots. args[4]
+ * and args[5] are packed for the DSP but must not be registered there,
+ * and are never walked because the argument loops stop at nscalars.
+ */
+ assign_kernel_gem_arg(args, ctx->gem_objs, 0, kernel_gem,
+ gv + off_inbuf, sizeof(*inbuf));
+ assign_kernel_gem_arg(args, ctx->gem_objs, 1, kernel_gem,
+ gv + off_comm, inbuf->namelen);
+ args[2].ptr = init->file;
+ args[2].length = inbuf->filelen;
+ args[2].handle = init->filehandle;
+ assign_kernel_gem_arg(args, ctx->gem_objs, 3, kernel_gem,
+ gv + off_pages, sizeof(struct fastrpc_phy_page));
+ args[4].ptr = (u64)(uintptr_t)(gv + off_attrs);
+ args[4].length = sizeof(u32);
+ args[5].ptr = (u64)(uintptr_t)(gv + off_siglen);
+ args[5].length = sizeof(u32);
+
+ drm_gem_object_put(&kernel_gem->base);
+
+ ctx->args = args;
+ ctx->inbuf = inbuf;
+
+ return 0;
+
+err_free_gem_objs:
+ kfree(ctx->gem_objs);
+ ctx->gem_objs = NULL;
+err_free_args:
+ kfree(args);
+
+ 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.
@@ -412,6 +617,14 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
int err;
switch (ctx->type) {
+ case FASTRPC_RMID_INIT_RELEASE:
+ err = qda_fastrpc_prepare_args_release_process(ctx);
+ break;
+ case FASTRPC_RMID_INIT_CREATE:
+ case FASTRPC_RMID_INIT_CREATE_ATTR:
+ ctx->pd = QDA_USER_PD;
+ err = qda_fastrpc_prepare_args_init_create(ctx, argp);
+ break;
case 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 a25818923a6e..6beaa9538983 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -107,6 +107,27 @@ struct fastrpc_invoke_buf {
u32 pgidx;
};
+/**
+ * struct fastrpc_create_process_inbuf - Input buffer for process creation
+ *
+ * This structure defines the input buffer format for creating a new
+ * process on the remote DSP.
+ */
+struct fastrpc_create_process_inbuf {
+ /** @remote_session_id: Client identifier for the session */
+ int remote_session_id;
+ /** @namelen: Length of the process name string including NUL terminator */
+ u32 namelen;
+ /** @filelen: Length of the ELF shell file 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 fastrpc_msg - FastRPC wire message for remote invocations
*
@@ -192,6 +213,8 @@ struct qda_fastrpc_invoke_ctx {
struct qda_gem_obj *msg_gem_obj;
/** @file_priv: DRM file private data */
struct drm_file *file_priv;
+ /** @init_mem_gem_obj: GEM object for PD initialization memory */
+ struct qda_gem_obj *init_mem_gem_obj;
/**
* @req: Request buffer for the internal init/map/unmap calls. Points
* into a kernel-owned GEM mapping tracked by @gem_objs, so it must
@@ -226,11 +249,23 @@ struct qda_msg {
};
/* Remote Method ID table - identifies initialization and control operations */
+#define FASTRPC_RMID_INIT_RELEASE 1 /* Release DSP process */
+#define FASTRPC_RMID_INIT_CREATE 6 /* Create DSP process */
+#define FASTRPC_RMID_INIT_CREATE_ATTR 7 /* Create DSP process with attributes */
#define FASTRPC_RMID_INVOKE_DYNAMIC 0xFFFFFFFFU /* Dynamic method invocation */
/* Common handle for initialization operations */
#define FASTRPC_INIT_HANDLE 0x1
+/* Protection Domain (PD) identifiers */
+#define QDA_ROOT_PD (0)
+#define QDA_USER_PD (1)
+
+/* Number of arguments for process creation */
+#define FASTRPC_CREATE_PROCESS_NARGS 6
+/* Maximum initialization file size (4 MB) */
+#define FASTRPC_INIT_FILELEN_MAX (4 * 1024 * 1024)
+
void qda_fastrpc_context_free(struct kref *ref);
void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx);
void qda_fastrpc_flush_pending(struct qda_dev *qdev);
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index b7ee4899ba74..34b71b82a020 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -106,6 +106,40 @@ static int qda_fastrpc_invoke(u32 type, struct drm_device *dev, void *data,
ctx->file_priv = file_priv;
ctx->remote_session_id = qda_file_priv->remote_session_id;
+ if (type == FASTRPC_RMID_INIT_CREATE) {
+ /*
+ * A second INIT_CREATE on the same file without an intervening
+ * INIT_RELEASE replaces the process image, so drop the
+ * initialisation memory cached for the previous attempt.
+ */
+ if (qda_file_priv->init_mem_gem_obj) {
+ drm_gem_object_put(&qda_file_priv->init_mem_gem_obj->base);
+ qda_file_priv->init_mem_gem_obj = NULL;
+ }
+
+ gem_obj = qda_gem_create_object(dev, qdev->iommu_mgr,
+ FASTRPC_INIT_FILELEN_MAX, file_priv);
+ if (IS_ERR(gem_obj)) {
+ err = PTR_ERR(gem_obj);
+ goto err_context_free;
+ }
+
+ ctx->init_mem_gem_obj = to_qda_gem_obj(gem_obj);
+
+ /*
+ * The file private data caches the initialisation memory for
+ * the lifetime of the DSP process, so it holds a reference of
+ * its own; the context drops the one taken here when it is
+ * released.
+ */
+ drm_gem_object_get(gem_obj);
+ qda_file_priv->init_mem_gem_obj = ctx->init_mem_gem_obj;
+ } else if (type == FASTRPC_RMID_INIT_RELEASE) {
+ ctx->init_mem_gem_obj = qda_file_priv->init_mem_gem_obj;
+ if (ctx->init_mem_gem_obj)
+ drm_gem_object_get(&ctx->init_mem_gem_obj->base);
+ }
+
err = qda_fastrpc_prepare_args(ctx, data);
if (err)
goto err_context_free;
@@ -144,16 +178,59 @@ static int qda_fastrpc_invoke(u32 type, struct drm_device *dev, void *data,
qda_fastrpc_cleanup_handlelist(ctx);
+ /*
+ * The DSP has torn down the protection domain, so the initialisation
+ * memory can be released now.
+ */
+ if (type == FASTRPC_RMID_INIT_RELEASE && qda_file_priv->init_mem_gem_obj) {
+ drm_gem_object_put(&qda_file_priv->init_mem_gem_obj->base);
+ qda_file_priv->init_mem_gem_obj = NULL;
+ }
+
kref_put(&ctx->refcount, qda_fastrpc_context_free);
return 0;
err_context_free:
+ /*
+ * Process creation failed, so release the initialisation memory cached
+ * in the file private data; the context drops its own reference below.
+ */
+ if (type == FASTRPC_RMID_INIT_CREATE && qda_file_priv->init_mem_gem_obj) {
+ drm_gem_object_put(&qda_file_priv->init_mem_gem_obj->base);
+ qda_file_priv->init_mem_gem_obj = NULL;
+ }
+
kref_put(&ctx->refcount, qda_fastrpc_context_free);
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(FASTRPC_RMID_INIT_CREATE, dev, data, file_priv);
+}
+
+/**
+ * qda_release_dsp_process() - Release DSP process resources for a file
+ * @qdev: QDA device structure
+ * @file_priv: DRM file private data
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int qda_release_dsp_process(struct qda_dev *qdev, struct drm_file *file_priv)
+{
+ return qda_fastrpc_invoke(FASTRPC_RMID_INIT_RELEASE, &qdev->drm_dev, NULL, file_priv);
+}
+
/**
* qda_ioctl_invoke() - Perform a dynamic FastRPC method invocation
* @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 3bb9cfd98370..192565434363 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -9,6 +9,7 @@
#include "qda_drv.h"
int qda_ioctl_query(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_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);
diff --git a/include/uapi/drm/qda_accel.h b/include/uapi/drm/qda_accel.h
index 5cf0fec1d91c..d12b8dc44fdd 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -21,7 +21,8 @@ extern "C" {
#define DRM_QDA_QUERY 0x00
#define DRM_QDA_GEM_CREATE 0x01
#define DRM_QDA_GEM_MMAP_OFFSET 0x02
-/* Command numbers 0x03-0x06 reserved for INIT_ATTACH, INIT_CREATE, MAP, MUNMAP */
+/* Command number 0x03 reserved for INIT_ATTACH; 0x05-0x06 reserved for MAP, MUNMAP */
+#define DRM_QDA_REMOTE_SESSION_CREATE 0x04
#define DRM_QDA_REMOTE_INVOKE 0x07
/*
@@ -37,6 +38,9 @@ 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_SESSION_CREATE \
+ DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_SESSION_CREATE, \
+ struct drm_qda_init_create)
#define DRM_IOCTL_QDA_REMOTE_INVOKE DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_INVOKE, \
struct drm_qda_invoke_args)
@@ -126,6 +130,31 @@ struct drm_qda_invoke_args {
__u64 args;
};
+/**
+ * struct drm_qda_init_create - Accelerator process initialization parameters
+ * @filelen: Length of the ELF file in bytes
+ * @filehandle: GEM handle for the buffer containing the ELF file; 0 if using
+ * the direct file pointer instead
+ * @attrs: Process attributes flags
+ * @siglen: Length of signature data in bytes
+ * @file: Pointer to ELF file data if not using filehandle
+ *
+ * This structure is used with DRM_IOCTL_QDA_INIT_CREATE to initialize
+ * a new process on the accelerator. The process code is provided either
+ * via a GEM handle (filehandle) or a direct pointer (file).
+ * Set file to 0 if using filehandle.
+ *
+ * The attrs field contains bit flags for debug mode, privileged execution,
+ * and other process attributes.
+ */
+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] 18+ messages in thread
* [PATCH v2 14/15] accel/qda: Add remote memory mapping to DSP address space
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
` (12 preceding siblings ...)
2026-08-17 4:47 ` [PATCH v2 13/15] accel/qda: Add DSP process creation and release Ekansh Gupta
@ 2026-08-17 4:47 ` Ekansh Gupta
13 siblings, 0 replies; 18+ messages in thread
From: Ekansh Gupta @ 2026-08-17 4:47 UTC (permalink / raw)
To: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König
Cc: Bharath Kumar, Chenna Kesava Raju, srinivas.kandagatla,
dmitry.baryshkov, linux-kernel, dri-devel, linux-doc,
linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig,
Ekansh Gupta
Add DRM_IOCTL_QDA_REMOTE_MAP, which maps a GEM buffer into the DSP
address space of the calling process so the DSP can access it directly
during a remote invocation.
The DSP exposes two mapping operations, both reachable through this
IOCTL and selected by the request field of struct drm_qda_mem_map:
QDA_REQUEST_MMAP maps a whole buffer, optionally at a caller-supplied
address hint. It uses the handle, flags, vaddrin and size fields.
QDA_REQUEST_MEM_MAP maps a region of a buffer with caller-specified
SMMU attributes, and additionally carries a DSP-side buffer
identifier. It uses all fields, including attrs and offset.
In both cases the buffer is identified by a GEM handle; user space
imports any DMA-BUF fd to a GEM handle before mapping it. The driver
resolves the handle to its context bank address, describes it to the DSP
as a physical page descriptor, and returns the resulting DSP virtual
address in vaddrout.
The requested length is validated against the size of the backing GEM
object. The offset is validated by the DSP against the region it maps,
since it only ever resolves within the context bank assigned to the
calling process.
qda_fastrpc.c gains a prepare_args builder per request type, each
documented with the packed argument layout the DSP expects, and the
response handler stores the DSP-returned address back into the caller's
argument structure.
Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
---
drivers/accel/qda/qda_drv.c | 1 +
drivers/accel/qda/qda_fastrpc.c | 243 ++++++++++++++++++++++++++++++++++++++++
drivers/accel/qda/qda_fastrpc.h | 56 +++++++++
drivers/accel/qda/qda_ioctl.c | 23 ++++
drivers/accel/qda/qda_ioctl.h | 1 +
include/uapi/drm/qda_accel.h | 45 +++++++-
6 files changed, 368 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
index 4c3a37cbf49e..a29037bf8d63 100644
--- a/drivers/accel/qda/qda_drv.c
+++ b/drivers/accel/qda/qda_drv.c
@@ -61,6 +61,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_SESSION_CREATE, qda_ioctl_init_create, 0),
+ DRM_IOCTL_DEF_DRV(QDA_REMOTE_MAP, qda_ioctl_mmap, 0),
DRM_IOCTL_DEF_DRV(QDA_REMOTE_INVOKE, qda_ioctl_invoke, 0),
};
diff --git a/drivers/accel/qda/qda_fastrpc.c b/drivers/accel/qda/qda_fastrpc.c
index 2c72f86130aa..6bd706b9dad2 100644
--- a/drivers/accel/qda/qda_fastrpc.c
+++ b/drivers/accel/qda/qda_fastrpc.c
@@ -410,6 +410,70 @@ int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *
return 0;
}
+/**
+ * qda_fastrpc_return_result() - Propagate invocation results to the ioctl data
+ * @ctx: FastRPC invocation context
+ * @argp: Kernel-side ioctl argument buffer owned by the DRM core
+ *
+ * Every buffer argument is GEM backed, so the DSP writes its results straight
+ * into DMA-coherent memory that both sides can see and no copy back is needed.
+ * Only the mapping calls have to surface a value from the response buffer.
+ */
+void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ struct fastrpc_map_rsp_msg *rsp_msg;
+ struct drm_qda_mem_map *margs;
+
+ switch (ctx->type) {
+ case FASTRPC_RMID_INIT_MMAP:
+ case FASTRPC_RMID_INIT_MEM_MAP:
+ rsp_msg = ctx->rsp;
+ margs = argp;
+ margs->vaddrout = rsp_msg->vaddrout;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Fill in a physical page descriptor from a GEM handle. The reference taken
+ * by the lookup is dropped again here; the handle table keeps the object alive
+ * for the duration of the invocation.
+ */
+static int setup_mmap_pages(struct qda_fastrpc_invoke_ctx *ctx, u32 gem_handle,
+ u64 len, struct fastrpc_phy_page *pages)
+{
+ struct drm_gem_object *gem_obj;
+ struct qda_gem_obj *qda_gem_obj;
+ int err;
+
+ if (!gem_handle) {
+ pages->addr = 0;
+ pages->size = 0;
+ return 0;
+ }
+
+ err = get_gem_obj_from_handle(ctx, gem_handle, &gem_obj);
+ if (err)
+ return err;
+
+ qda_gem_obj = to_qda_gem_obj(gem_obj);
+
+ /* The requested region must fit within the backing GEM object */
+ if (len > qda_gem_obj->size) {
+ drm_gem_object_put(gem_obj);
+ return -EINVAL;
+ }
+
+ pages->addr = qda_gem_obj->dma_addr;
+ pages->size = len ? len : qda_gem_obj->size;
+
+ drm_gem_object_put(gem_obj);
+
+ return 0;
+}
+
/*
* INIT_RELEASE packed layout:
* [0] remote_session_id sizeof(int) IN
@@ -573,6 +637,179 @@ static int qda_fastrpc_prepare_args_init_create(struct qda_fastrpc_invoke_ctx *c
return err;
}
+/*
+ * INIT_MMAP packed layout:
+ * [0] req_msg sizeof(fastrpc_map_req_msg) IN
+ * [1] input_pages sizeof(fastrpc_phy_page) IN
+ * [2] rsp_msg sizeof(fastrpc_map_rsp_msg) OUT
+ */
+static int qda_fastrpc_prepare_args_map(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ struct fastrpc_map_req_msg *req_msg;
+ struct fastrpc_map_rsp_msg *rsp_msg;
+ struct drm_qda_fastrpc_invoke_args *args;
+ struct drm_qda_mem_map *margs = argp;
+ struct qda_gem_obj *kernel_gem;
+ u32 nscalars;
+ int err;
+ u8 *gv;
+
+ const size_t off_req = 0;
+ const size_t off_pages = off_req + sizeof(*req_msg);
+ const size_t off_rsp = off_pages + sizeof(struct fastrpc_phy_page);
+ const size_t total_size = off_rsp + sizeof(*rsp_msg);
+
+ ctx->sc = FASTRPC_SCALARS(FASTRPC_RMID_INIT_MMAP, 2, 1);
+ ctx->handle = FASTRPC_INIT_HANDLE;
+ nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+
+ args = kzalloc_objs(*args, nscalars);
+ if (!args)
+ return -ENOMEM;
+
+ ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
+ if (!ctx->gem_objs) {
+ err = -ENOMEM;
+ goto err_free_args;
+ }
+
+ kernel_gem = alloc_kernel_gem(ctx, total_size);
+ if (IS_ERR(kernel_gem)) {
+ err = PTR_ERR(kernel_gem);
+ goto err_free_gem_objs;
+ }
+ gv = kernel_gem->virt;
+
+ req_msg = (struct fastrpc_map_req_msg *)(gv + off_req);
+ req_msg->remote_session_id = ctx->remote_session_id;
+ req_msg->flags = margs->flags;
+ req_msg->vaddr = margs->vaddrin;
+ req_msg->num = sizeof(struct fastrpc_phy_page);
+
+ ctx->input_pages = (struct fastrpc_phy_page *)(gv + off_pages);
+ err = setup_mmap_pages(ctx, margs->handle, margs->size, ctx->input_pages);
+ if (err)
+ goto err_free_kernel_gem;
+
+ /* Already zeroed; the DSP writes vaddrout here */
+ rsp_msg = (struct fastrpc_map_rsp_msg *)(gv + off_rsp);
+
+ assign_kernel_gem_arg(args, ctx->gem_objs, 0, kernel_gem,
+ gv + off_req, sizeof(*req_msg));
+ assign_kernel_gem_arg(args, ctx->gem_objs, 1, kernel_gem,
+ gv + off_pages, sizeof(struct fastrpc_phy_page));
+ assign_kernel_gem_arg(args, ctx->gem_objs, 2, kernel_gem,
+ gv + off_rsp, sizeof(*rsp_msg));
+
+ drm_gem_object_put(&kernel_gem->base);
+
+ ctx->args = args;
+ ctx->req = req_msg;
+ ctx->rsp = rsp_msg;
+
+ return 0;
+
+err_free_kernel_gem:
+ drm_gem_object_put(&kernel_gem->base);
+err_free_gem_objs:
+ kfree(ctx->gem_objs);
+ ctx->gem_objs = NULL;
+err_free_args:
+ kfree(args);
+
+ return err;
+}
+
+/*
+ * INIT_MEM_MAP packed layout:
+ * [0] req_msg sizeof(fastrpc_mem_map_req_msg) IN
+ * [1] input_pages sizeof(fastrpc_phy_page) IN
+ * [2] zero-length 0 bytes, required by the DSP IN
+ * [3] rsp_msg sizeof(fastrpc_map_rsp_msg) OUT
+ */
+static int qda_fastrpc_prepare_args_mem_map_attr(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
+{
+ struct fastrpc_mem_map_req_msg *req_msg;
+ struct fastrpc_map_rsp_msg *rsp_msg;
+ struct drm_qda_fastrpc_invoke_args *args;
+ struct drm_qda_mem_map *margs = argp;
+ struct qda_gem_obj *kernel_gem;
+ u32 nscalars;
+ int err;
+ u8 *gv;
+
+ const size_t off_req = 0;
+ const size_t off_pages = off_req + sizeof(*req_msg);
+ const size_t off_rsp = off_pages + sizeof(struct fastrpc_phy_page);
+ const size_t total_size = off_rsp + sizeof(*rsp_msg);
+
+ ctx->sc = FASTRPC_SCALARS(FASTRPC_RMID_INIT_MEM_MAP, 3, 1);
+ ctx->handle = FASTRPC_INIT_HANDLE;
+ nscalars = REMOTE_SCALARS_LENGTH(ctx->sc);
+
+ args = kzalloc_objs(*args, nscalars);
+ if (!args)
+ return -ENOMEM;
+
+ ctx->gem_objs = kzalloc_objs(*ctx->gem_objs, nscalars);
+ if (!ctx->gem_objs) {
+ err = -ENOMEM;
+ goto err_free_args;
+ }
+
+ kernel_gem = alloc_kernel_gem(ctx, total_size);
+ if (IS_ERR(kernel_gem)) {
+ err = PTR_ERR(kernel_gem);
+ goto err_free_gem_objs;
+ }
+ gv = kernel_gem->virt;
+
+ req_msg = (struct fastrpc_mem_map_req_msg *)(gv + off_req);
+ req_msg->remote_session_id = ctx->remote_session_id;
+ req_msg->handle = margs->dsp_handle;
+ req_msg->offset = margs->offset;
+ req_msg->flags = margs->flags;
+ req_msg->vaddrin = margs->vaddrin;
+ req_msg->num = sizeof(struct fastrpc_phy_page);
+ req_msg->data_len = 0;
+
+ ctx->input_pages = (struct fastrpc_phy_page *)(gv + off_pages);
+ err = setup_mmap_pages(ctx, margs->handle, margs->size, ctx->input_pages);
+ if (err)
+ goto err_free_kernel_gem;
+
+ /* Already zeroed; the DSP writes vaddrout here */
+ rsp_msg = (struct fastrpc_map_rsp_msg *)(gv + off_rsp);
+
+ assign_kernel_gem_arg(args, ctx->gem_objs, 0, kernel_gem,
+ gv + off_req, sizeof(*req_msg));
+ assign_kernel_gem_arg(args, ctx->gem_objs, 1, kernel_gem,
+ gv + off_pages, sizeof(struct fastrpc_phy_page));
+ /* Zero-length entry required by the DSP protocol */
+ assign_kernel_gem_arg(args, ctx->gem_objs, 2, kernel_gem,
+ gv + off_pages, 0);
+ assign_kernel_gem_arg(args, ctx->gem_objs, 3, kernel_gem,
+ gv + off_rsp, sizeof(*rsp_msg));
+
+ drm_gem_object_put(&kernel_gem->base);
+
+ ctx->args = args;
+ ctx->req = req_msg;
+ ctx->rsp = rsp_msg;
+
+ return 0;
+
+err_free_kernel_gem:
+ drm_gem_object_put(&kernel_gem->base);
+err_free_gem_objs:
+ kfree(ctx->gem_objs);
+ ctx->gem_objs = NULL;
+err_free_args:
+ kfree(args);
+
+ 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.
@@ -625,6 +862,12 @@ int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp)
ctx->pd = QDA_USER_PD;
err = qda_fastrpc_prepare_args_init_create(ctx, argp);
break;
+ case FASTRPC_RMID_INIT_MMAP:
+ err = qda_fastrpc_prepare_args_map(ctx, argp);
+ break;
+ case FASTRPC_RMID_INIT_MEM_MAP:
+ err = qda_fastrpc_prepare_args_mem_map_attr(ctx, argp);
+ break;
case 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 6beaa9538983..0c1264380085 100644
--- a/drivers/accel/qda/qda_fastrpc.h
+++ b/drivers/accel/qda/qda_fastrpc.h
@@ -250,8 +250,10 @@ struct qda_msg {
/* Remote Method ID table - identifies initialization and control operations */
#define FASTRPC_RMID_INIT_RELEASE 1 /* Release DSP process */
+#define FASTRPC_RMID_INIT_MMAP 4 /* Map memory region to DSP */
#define FASTRPC_RMID_INIT_CREATE 6 /* Create DSP process */
#define FASTRPC_RMID_INIT_CREATE_ATTR 7 /* Create DSP process with attributes */
+#define FASTRPC_RMID_INIT_MEM_MAP 10 /* Map DMA buffer with attributes to DSP */
#define FASTRPC_RMID_INVOKE_DYNAMIC 0xFFFFFFFFU /* Dynamic method invocation */
/* Common handle for initialization operations */
@@ -266,6 +268,59 @@ struct qda_msg {
/* Maximum initialization file size (4 MB) */
#define FASTRPC_INIT_FILELEN_MAX (4 * 1024 * 1024)
+/* Message structures for internal FastRPC calls */
+
+/**
+ * struct fastrpc_mem_map_req_msg - Memory map request message with attributes
+ *
+ * This message structure is sent to the DSP to request mapping
+ * of a DMA buffer with custom attributes (ATTR request).
+ */
+struct fastrpc_mem_map_req_msg {
+ /** @remote_session_id: Client identifier for the session */
+ s32 remote_session_id;
+ /** @handle: Handle of the buffer to map */
+ s32 handle;
+ /** @offset: Byte offset within the buffer */
+ s32 offset;
+ /** @flags: Mapping flags (cache attributes, permissions) */
+ u32 flags;
+ /** @vaddrin: Virtual address hint for the DSP mapping */
+ u64 vaddrin;
+ /** @num: Size of the physical page descriptor array in bytes */
+ s32 num;
+ /** @data_len: Length of additional inline data */
+ s32 data_len;
+};
+
+/**
+ * struct fastrpc_map_req_msg - Legacy memory map request message
+ *
+ * This message structure is sent to the DSP to request mapping
+ * of a DMA buffer into the DSP's virtual address space.
+ */
+struct fastrpc_map_req_msg {
+ /** @remote_session_id: Client identifier for the session */
+ s32 remote_session_id;
+ /** @flags: Mapping flags (cache attributes, permissions) */
+ u32 flags;
+ /** @vaddr: Virtual address hint for the DSP mapping */
+ u64 vaddr;
+ /** @num: Size of the physical page descriptor array in bytes */
+ s32 num;
+};
+
+/**
+ * struct fastrpc_map_rsp_msg - Memory map response message
+ *
+ * This message structure is returned by the DSP after successfully
+ * mapping a buffer, providing the virtual address for future access.
+ */
+struct fastrpc_map_rsp_msg {
+ /** @vaddrout: DSP virtual address assigned to the mapped buffer */
+ u64 vaddrout;
+};
+
void qda_fastrpc_context_free(struct kref *ref);
void qda_fastrpc_cleanup_handlelist(struct qda_fastrpc_invoke_ctx *ctx);
void qda_fastrpc_flush_pending(struct qda_dev *qdev);
@@ -273,5 +328,6 @@ struct qda_fastrpc_invoke_ctx *qda_fastrpc_context_alloc(struct qda_dev *qdev);
int qda_fastrpc_prepare_args(struct qda_fastrpc_invoke_ctx *ctx, void *argp);
size_t qda_fastrpc_get_header_size(struct qda_fastrpc_invoke_ctx *ctx);
int qda_fastrpc_invoke_pack(struct qda_fastrpc_invoke_ctx *ctx, struct qda_msg *msg);
+void qda_fastrpc_return_result(struct qda_fastrpc_invoke_ctx *ctx, void *argp);
#endif /* __QDA_FASTRPC_H__ */
diff --git a/drivers/accel/qda/qda_ioctl.c b/drivers/accel/qda/qda_ioctl.c
index 34b71b82a020..3ff6b598ee78 100644
--- a/drivers/accel/qda/qda_ioctl.c
+++ b/drivers/accel/qda/qda_ioctl.c
@@ -177,6 +177,7 @@ static int qda_fastrpc_invoke(u32 type, struct drm_device *dev, void *data,
}
qda_fastrpc_cleanup_handlelist(ctx);
+ qda_fastrpc_return_result(ctx, data);
/*
* The DSP has torn down the protection domain, so the initialisation
@@ -231,6 +232,28 @@ int qda_release_dsp_process(struct qda_dev *qdev, struct drm_file *file_priv)
return qda_fastrpc_invoke(FASTRPC_RMID_INIT_RELEASE, &qdev->drm_dev, NULL, file_priv);
}
+/**
+ * qda_ioctl_mmap() - Map memory to DSP 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(FASTRPC_RMID_INIT_MMAP, dev, data, file_priv);
+ case QDA_REQUEST_MEM_MAP:
+ return qda_fastrpc_invoke(FASTRPC_RMID_INIT_MEM_MAP, dev, data, file_priv);
+ default:
+ return -EINVAL;
+ }
+}
+
/**
* qda_ioctl_invoke() - Perform a dynamic FastRPC method invocation
* @dev: DRM device structure
diff --git a/drivers/accel/qda/qda_ioctl.h b/drivers/accel/qda/qda_ioctl.h
index 192565434363..457ceccede08 100644
--- a/drivers/accel/qda/qda_ioctl.h
+++ b/drivers/accel/qda/qda_ioctl.h
@@ -13,5 +13,6 @@ int qda_ioctl_init_create(struct drm_device *dev, void *data, struct drm_file *f
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_mmap(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 d12b8dc44fdd..046a1ef1872d 100644
--- a/include/uapi/drm/qda_accel.h
+++ b/include/uapi/drm/qda_accel.h
@@ -21,8 +21,9 @@ extern "C" {
#define DRM_QDA_QUERY 0x00
#define DRM_QDA_GEM_CREATE 0x01
#define DRM_QDA_GEM_MMAP_OFFSET 0x02
-/* Command number 0x03 reserved for INIT_ATTACH; 0x05-0x06 reserved for MAP, MUNMAP */
+/* Command number 0x03 reserved for INIT_ATTACH; 0x06 reserved for MUNMAP */
#define DRM_QDA_REMOTE_SESSION_CREATE 0x04
+#define DRM_QDA_REMOTE_MAP 0x05
#define DRM_QDA_REMOTE_INVOKE 0x07
/*
@@ -41,9 +42,15 @@ 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_MAP DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_MAP, \
+ struct drm_qda_mem_map)
#define DRM_IOCTL_QDA_REMOTE_INVOKE DRM_IOWR(DRM_COMMAND_BASE + DRM_QDA_REMOTE_INVOKE, \
struct drm_qda_invoke_args)
+/* Request type definitions for qda_mem_map */
+#define QDA_REQUEST_MMAP 1 /* MMAP operation */
+#define QDA_REQUEST_MEM_MAP 2 /* Handle-based MEM_MAP operation with attributes */
+
/* Query type definitions for drm_qda_query */
#define QDA_QUERY_DSP_NAME 1
@@ -155,6 +162,42 @@ struct drm_qda_init_create {
__u64 file;
};
+/**
+ * struct drm_qda_mem_map - Memory mapping request structure
+ * @request: Request type (QDA_REQUEST_MMAP or QDA_REQUEST_MEM_MAP)
+ * @flags: Mapping flags for DSP (cache attributes, permissions)
+ * @handle: GEM handle of the buffer to map (used for kernel buffer lookup)
+ * @attrs: Mapping attributes (used for ATTR request)
+ * @offset: Offset within buffer (used for ATTR request)
+ * @dsp_handle: Handle forwarded to DSP as the buffer identifier
+ * @vaddrin: Optional virtual address hint for mapping
+ * @size: Size of the memory region to map in bytes
+ * @vaddrout: Output DSP virtual address after successful mapping
+ *
+ * This structure is used to request mapping of a GEM buffer into the
+ * DSP's virtual address space. The DSP will map the buffer according
+ * to the specified flags and return the virtual address in vaddrout.
+ *
+ * For QDA_REQUEST_MMAP (value 1):
+ * - Uses fields: handle, flags, vaddrin, size, vaddrout
+ * - Legacy MMAP operation for backward compatibility
+ *
+ * For QDA_REQUEST_MEM_MAP (value 2):
+ * - Uses all fields including attrs and offset
+ * - Handle-based MEM_MAP operation with custom SMMU attributes
+ */
+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] 18+ messages in thread
* Re: [PATCH v2 07/15] accel/qda: Add memory manager for CB devices
2026-08-17 4:47 ` [PATCH v2 07/15] accel/qda: Add memory manager for CB devices Ekansh Gupta
@ 2026-08-17 6:52 ` Dmitry Baryshkov
0 siblings, 0 replies; 18+ messages in thread
From: Dmitry Baryshkov @ 2026-08-17 6:52 UTC (permalink / raw)
To: Ekansh Gupta
Cc: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König, Bharath Kumar,
Chenna Kesava Raju, srinivas.kandagatla, linux-kernel, dri-devel,
linux-doc, linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig
On Mon, Aug 17, 2026 at 10:17:42AM +0530, Ekansh Gupta wrote:
> Introduce the QDA memory manager (qda_memory_manager) to track the
> IOMMU devices that back each compute context bank (CB).
>
> Each CB device registered on the qda-compute-cb bus is wrapped in a
> qda_iommu_device descriptor recording the device pointer and its stream
> ID, and stored in the memory manager's registry. Later patches use this
> registry to resolve the IOMMU device a session should allocate from.
>
> The registry is a plain array sized to the number of
> "qcom,fastrpc-compute-cb" nodes present in the device tree: the RPMsg
> probe counts those nodes and passes the count to qda_init_device(),
> which allocates the array in qda_memory_manager_init(). The memory
> manager is created before CB devices are populated and destroyed after
> they are torn down, so no dangling descriptors remain.
>
> qda_cb_setup_device() is called immediately after a CB device is
> registered on the bus: it allocates the descriptor, registers it with
> the memory manager, and stores it as the CB device's driver data so
> that qda_destroy_cb_device() can unregister and free it during teardown.
>
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
> ---
> Changes in v2:
> - Replace the XArray with a plain array sized to the device tree's CB
> node count instead of a fixed 16-entry table (Dmitry Baryshkov)
> - Fold the init_memory_manager()/cleanup_memory_manager() wrappers into
> qda_init_device()/qda_deinit_device() (Dmitry Baryshkov)
> - Drop the pr_debug() calls (Dmitry Baryshkov)
> - Use goto labels to unwind probe failures instead of open-coding the
> cleanup at each error site (Dmitry Baryshkov)
> ---
> drivers/accel/qda/Makefile | 1 +
> drivers/accel/qda/qda_cb.c | 39 ++++++++++++++
> drivers/accel/qda/qda_drv.c | 26 +++++++++
> drivers/accel/qda/qda_drv.h | 5 ++
> drivers/accel/qda/qda_memory_manager.c | 98 ++++++++++++++++++++++++++++++++++
> drivers/accel/qda/qda_memory_manager.h | 55 +++++++++++++++++++
> drivers/accel/qda/qda_rpmsg.c | 23 +++++++-
> 7 files changed, 246 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/accel/qda/Makefile b/drivers/accel/qda/Makefile
> index 143c9e4e789e..701fad5ffb50 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_memory_manager.o \
> qda_rpmsg.o
>
> obj-$(CONFIG_DRM_ACCEL_QDA_COMPUTE_BUS) += qda_compute_bus.o
> diff --git a/drivers/accel/qda/qda_cb.c b/drivers/accel/qda/qda_cb.c
> index 9c5ef32f351e..cd32d6df6318 100644
> --- a/drivers/accel/qda/qda_cb.c
> +++ b/drivers/accel/qda/qda_cb.c
> @@ -8,13 +8,40 @@
> #include <linux/slab.h>
> #include <drm/drm_print.h>
> #include "qda_drv.h"
> +#include "qda_memory_manager.h"
> #include "qda_cb.h"
>
> +static int qda_cb_setup_device(struct qda_dev *qdev, struct device *cb_dev, u32 sid)
> +{
> + 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;
> + iommu_dev->sid = sid;
> +
> + ret = qda_memory_manager_register_device(qdev->iommu_mgr, iommu_dev);
> + if (ret) {
> + drm_err(&qdev->drm_dev, "Failed to register IOMMU device: %d\n", ret);
> + kfree(iommu_dev);
> + return ret;
> + }
> +
> + dev_set_drvdata(cb_dev, iommu_dev);
> +
> + return 0;
> +}
> +
> int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
> {
> struct qda_cb_dev *entry;
> struct device *cb_dev;
> u32 sid = 0;
> + int ret;
>
> if (of_property_read_u32(cb_node, "reg", &sid))
> drm_info(&qdev->drm_dev, "No stream ID in DT node %pOFn, using 0\n", cb_node);
> @@ -27,6 +54,12 @@ int qda_create_cb_device(struct qda_dev *qdev, struct device_node *cb_node)
> return PTR_ERR(cb_dev);
> }
>
> + ret = qda_cb_setup_device(qdev, cb_dev, sid);
> + if (ret) {
> + device_unregister(cb_dev);
> + return ret;
> + }
> +
> entry = kzalloc_obj(*entry);
> if (!entry) {
> qda_destroy_cb_device(cb_dev);
> @@ -74,8 +107,14 @@ int qda_cb_populate(struct qda_dev *qdev, struct device_node *parent_node)
>
> void qda_destroy_cb_device(struct device *cb_dev)
> {
> + struct qda_iommu_device *iommu_dev;
> struct iommu_group *group;
>
> + iommu_dev = dev_get_drvdata(cb_dev);
> + if (iommu_dev && iommu_dev->qdev->iommu_mgr)
Can either of them be NULL?
> + qda_memory_manager_unregister_device(iommu_dev->qdev->iommu_mgr,
> + iommu_dev);
> +
> group = iommu_group_get(cb_dev);
> if (group) {
> iommu_group_remove_device(cb_dev);
> diff --git a/drivers/accel/qda/qda_drv.c b/drivers/accel/qda/qda_drv.c
> index 09b6d5ac390b..fdc909facd95 100644
> --- a/drivers/accel/qda/qda_drv.c
> +++ b/drivers/accel/qda/qda_drv.c
> @@ -56,6 +56,32 @@ struct qda_dev *qda_alloc_device(struct device *dev)
> return qdev;
> }
>
> +void qda_deinit_device(struct qda_dev *qdev)
> +{
> + if (qdev->iommu_mgr) {
Why?
> + qda_memory_manager_exit(qdev->iommu_mgr);
> + kfree(qdev->iommu_mgr);
> + qdev->iommu_mgr = NULL;
What for? If it's a teardown path, qdev will be destroyed soon.
> + }
> +}
> +
> +int qda_init_device(struct qda_dev *qdev, int num_cbs)
> +{
> + int ret;
> +
> + qdev->iommu_mgr = kzalloc_obj(*qdev->iommu_mgr);
devm_kzalloc_obj()?
> + 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);
> + kfree(qdev->iommu_mgr);
> + qdev->iommu_mgr = NULL;
And what is going to read this NULL?
> + }
> +
> + return ret;
> +}
> +
> 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 3bd41b9593fa..690a833d732b 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"
>
> /* Driver identification */
> #define QDA_DRIVER_NAME "qda"
> @@ -40,6 +41,8 @@ struct qda_dev {
> struct device *dev;
> /** @cb_devs: Compute context-bank (CB) child devices */
> struct list_head cb_devs;
> + /** @iommu_mgr: IOMMU/memory manager instance */
> + struct qda_memory_manager *iommu_mgr;
> /** @dsp_name: Name of the DSP domain (e.g. "cdsp", "adsp") */
> const char *dsp_name;
> };
> @@ -59,6 +62,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..b1a80ee77c35
> --- /dev/null
> +++ b/drivers/accel/qda/qda_memory_manager.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> +
> +#include <linux/slab.h>
> +#include "qda_drv.h"
> +#include "qda_memory_manager.h"
> +
> +static void cleanup_all_memory_devices(struct qda_memory_manager *mem_mgr)
> +{
> + int i;
> +
> + for (i = 0; i < mem_mgr->num_devices; i++) {
> + struct qda_iommu_device *iommu_dev = mem_mgr->devices[i];
> +
> + if (!iommu_dev)
> + continue;
> +
> + mem_mgr->devices[i] = NULL;
> + kfree(iommu_dev);
> + }
> + mem_mgr->num_devices = 0;
What for?
> +}
> +
> +/**
> + * qda_memory_manager_register_device() - Register an IOMMU device
> + * @mem_mgr: Pointer to memory manager
> + * @iommu_dev: Pointer to IOMMU device to register
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int qda_memory_manager_register_device(struct qda_memory_manager *mem_mgr,
> + struct qda_iommu_device *iommu_dev)
> +{
> + if (mem_mgr->num_devices >= mem_mgr->max_devices)
> + return -ENOSPC;
> +
> + iommu_dev->id = mem_mgr->num_devices;
> + mem_mgr->devices[mem_mgr->num_devices++] = iommu_dev;
> +
> + return 0;
> +}
> +
> +/**
> + * qda_memory_manager_unregister_device() - Unregister an IOMMU device
> + * @mem_mgr: Pointer to memory manager
> + * @iommu_dev: Pointer to IOMMU device to unregister
> + *
> + * Removes the device from the registry and frees it.
> + */
> +void qda_memory_manager_unregister_device(struct qda_memory_manager *mem_mgr,
> + struct qda_iommu_device *iommu_dev)
> +{
> + int i;
> +
> + for (i = 0; i < mem_mgr->num_devices; i++) {
> + if (mem_mgr->devices[i] == iommu_dev) {
> + mem_mgr->devices[i] = NULL;
> + break;
> + }
> + }
> +
> + kfree(iommu_dev);
> +}
> +
> +/**
> + * qda_memory_manager_init() - Initialize the memory manager
> + * @mem_mgr: Pointer to memory manager structure to initialize
> + * @max_devices: Number of IOMMU context bank devices 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->num_devices = 0;
> + mem_mgr->max_devices = max_devices;
> +
> + return 0;
> +}
> +
> +/**
> + * qda_memory_manager_exit() - Clean up the memory manager
> + * @mem_mgr: Pointer to memory manager structure to clean up
> + */
> +void qda_memory_manager_exit(struct qda_memory_manager *mem_mgr)
> +{
> + cleanup_all_memory_devices(mem_mgr);
> +
> + kfree(mem_mgr->devices);
> + mem_mgr->devices = NULL;
> + mem_mgr->max_devices = 0;
> +}
> diff --git a/drivers/accel/qda/qda_memory_manager.h b/drivers/accel/qda/qda_memory_manager.h
> new file mode 100644
> index 000000000000..7e38c8a18284
> --- /dev/null
> +++ b/drivers/accel/qda/qda_memory_manager.h
> @@ -0,0 +1,55 @@
> +/* 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>
> +
> +/* Forward declarations */
> +struct qda_dev;
> +
> +/**
> + * struct qda_iommu_device - IOMMU device instance for memory management
> + *
> + * Represents a single IOMMU-enabled device managed by the memory manager.
> + * Each device can be assigned to a specific process session.
> + */
> +struct qda_iommu_device {
> + /** @dev: Pointer to the underlying device */
> + struct device *dev;
> + /** @qdev: Back-pointer to the parent QDA device */
> + struct qda_dev *qdev;
> + /** @id: Unique identifier assigned by the memory manager */
> + u32 id;
What kind of identifier? What is going to use it? I don't see any
readers in this patch, please don't add useless data.
> + /** @sid: Stream ID for IOMMU transactions */
> + u32 sid;
Do you need to store it?
> +};
> +
> +/**
> + * struct qda_memory_manager - Central memory management coordinator
> + *
> + * Coordinates memory management across multiple IOMMU devices. Maintains
> + * a registry of devices in an array sized to the number of context banks
> + * described in the device tree.
> + */
> +struct qda_memory_manager {
> + /** @devices: Array storing registered IOMMU devices */
> + struct qda_iommu_device **devices;
> + /** @num_devices: Number of registered IOMMU devices */
> + int num_devices;
What for? Is devices array to be looped up to num_devices or
max_devices?
> + /** @max_devices: Capacity of the @devices array */
> + int max_devices;
> +};
> +
> +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);
> +
> +#endif /* __QDA_MEMORY_MANAGER_H__ */
> diff --git a/drivers/accel/qda/qda_rpmsg.c b/drivers/accel/qda/qda_rpmsg.c
> index 723b3e61bf0a..64bf503106d9 100644
> --- a/drivers/accel/qda/qda_rpmsg.c
> +++ b/drivers/accel/qda/qda_rpmsg.c
> @@ -42,11 +42,25 @@ 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)
> +{
> + struct device_node *child;
> + int count = 0;
> +
> + for_each_child_of_node(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)
> {
> struct qda_dev *qdev;
> + int num_cbs;
> int ret;
>
> qdev = qda_rpmsg_alloc_and_init_qdev(rpdev);
> @@ -59,10 +73,15 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
> return ret;
> }
>
> - ret = qda_cb_populate(qdev, rpdev->dev.of_node);
> + num_cbs = qda_count_cb_nodes(rpdev->dev.of_node);
> + ret = qda_init_device(qdev, num_cbs);
> 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)
> goto err_unpopulate;
> @@ -71,6 +90,8 @@ static int qda_rpmsg_probe(struct rpmsg_device *rpdev)
>
> err_unpopulate:
> qda_cb_unpopulate(qdev);
> +err_deinit:
> + qda_deinit_device(qdev);
> return ret;
> }
>
>
> --
> 2.34.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header
2026-08-17 4:47 ` [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header Ekansh Gupta
@ 2026-08-17 6:58 ` Dmitry Baryshkov
0 siblings, 0 replies; 18+ messages in thread
From: Dmitry Baryshkov @ 2026-08-17 6:58 UTC (permalink / raw)
To: Ekansh Gupta
Cc: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Sumit Semwal, Christian König, Bharath Kumar,
Chenna Kesava Raju, srinivas.kandagatla, linux-kernel, dri-devel,
linux-doc, linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig
On Mon, Aug 17, 2026 at 10:17:43AM +0530, Ekansh Gupta wrote:
> Introduce DRM_IOCTL_QDA_QUERY, a query IOCTL that lets user-space
> retrieve information about the DSP a given /dev/accel/accel* node
> represents.
>
> The IOCTL takes a query_type selector as input, so it can be extended
> to return additional parameters (capabilities, attributes) in the
> future without adding new IOCTLs: drm_ioctl() zero-extends the argument
> structure, so new fields can be appended to struct drm_qda_query as
> long as they go at the end. The first supported query,
> QDA_QUERY_DSP_NAME, returns the DSP domain name (e.g. "cdsp", "adsp").
>
> The UAPI header include/uapi/drm/qda_accel.h defines the command number,
> the DRM_IOWR IOCTL definition, the query_type values, and struct
> drm_qda_query. It follows the standard DRM UAPI conventions: fixed-width
> types, a C++ extern "C" guard, and GPL-2.0-only WITH Linux-syscall-note
> licensing.
>
> qda_ioctl_query() validates the reserved pad field, dispatches on
> query_type, and copies the DSP name from qda_dev.dsp_name into the
> user-supplied buffer with strscpy(). Unknown query types are rejected
> with -EINVAL.
>
> qda_drv.c registers the qda_ioctls[] table with the drm_driver so the
> DRM core dispatches DRM_IOCTL_QDA_QUERY to qda_ioctl_query().
Please tell your AI agent to stop describing patch contents. Ask it to
describe the reasons for the change.
>
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
> ---
> Changes in v2:
> - Add a query_type input selector so the IOCTL can return different
> parameters in future, and switch DRM_IOR -> DRM_IOWR so the input
> reaches the kernel (Dmitry Baryshkov)
> - Reject unknown query types and a non-zero pad with -EINVAL
> +
> +/*
> + * QDA IOCTL command numbers
> + *
> + * These define the command numbers for QDA-specific IOCTLs.
> + * They are used with DRM_COMMAND_BASE to create the full IOCTL numbers.
> + */
> +#define DRM_QDA_QUERY 0x00
> +
> +/*
> + * QDA IOCTL definitions
> + *
> + * These macros define the actual IOCTL numbers used by userspace applications.
> + * They combine the command numbers with DRM_COMMAND_BASE and specify the
> + * data structure and direction (read/write) for each IOCTL.
Is it not obvious?
> + */
> +#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
Why is it necessary for the userspace?
> +
> +/**
> + * struct drm_qda_query - Device information query structure
> + * @query_type: Type of query (input)
> + * @pad: Padding for 64-bit alignment (must be zero)
> + * @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;
> + __u32 pad;
What for? If you have u8 array afterwards, you don't need the padding.
> + __u8 dsp_name[16];
> +};
> +
> +#if defined(__cplusplus)
> +}
> +#endif
> +
> +#endif /* __QDA_ACCEL_H__ */
>
> --
> 2.34.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses
2026-08-17 4:47 ` [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses Ekansh Gupta
@ 2026-08-17 7:01 ` Joerg Roedel (AMD)
0 siblings, 0 replies; 18+ messages in thread
From: Joerg Roedel (AMD) @ 2026-08-17 7:01 UTC (permalink / raw)
To: Ekansh Gupta
Cc: Oded Gabbay, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
Will Deacon, Robin Murphy, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Sumit Semwal,
Christian König, Bharath Kumar, Chenna Kesava Raju,
srinivas.kandagatla, dmitry.baryshkov, linux-kernel, dri-devel,
linux-doc, linux-arm-msm, llvm, iommu, linux-media, linaro-mm-sig
On Mon, Aug 17, 2026 at 10:17:40AM +0530, Ekansh Gupta wrote:
> Register the QDA compute context bank bus (qda-compute-cb) with the
> IOMMU subsystem by adding it to the iommu_buses[] array.
>
> The QDA driver creates synthetic devices on this bus to represent
> IOMMU context banks (CBs). Each CB device needs its own IOMMU domain
> so that the DSP memory manager can enforce per-session address space
> isolation. Without this registration, the IOMMU subsystem does not
> probe CB devices for IOMMU groups and of_dma_configure() in the bus
> dma_configure callback has no IOMMU domain to attach to.
>
> Assisted-by: Claude:claude-sonnet-5
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Signed-off-by: Ekansh Gupta <ekansh.gupta@oss.qualcomm.com>
> ---
> drivers/iommu/iommu.c | 4 ++++
> 1 file changed, 4 insertions(+)
Acked-by: Joerg Roedel <joerg.roedel@amd.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-08-17 7:01 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 4:47 [PATCH v2 00/15] accel/qda: Qualcomm DSP Accelerator driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 01/15] MAINTAINERS: Add entry for Qualcomm DSP Accelerator (QDA) driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 02/15] accel/qda: Add QDA driver documentation Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 03/15] accel/qda: Add initial QDA DRM accelerator driver Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 04/15] accel/qda: Add compute bus for QDA context banks Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 05/15] iommu: Add QDA compute context bank bus to iommu_buses Ekansh Gupta
2026-08-17 7:01 ` Joerg Roedel (AMD)
2026-08-17 4:47 ` [PATCH v2 06/15] accel/qda: Create compute context bank devices on QDA compute bus Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 07/15] accel/qda: Add memory manager for CB devices Ekansh Gupta
2026-08-17 6:52 ` Dmitry Baryshkov
2026-08-17 4:47 ` [PATCH v2 08/15] accel/qda: Add QUERY IOCTL and QDA UAPI header Ekansh Gupta
2026-08-17 6:58 ` Dmitry Baryshkov
2026-08-17 4:47 ` [PATCH v2 09/15] accel/qda: Add DMA-backed GEM objects and memory manager integration Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 10/15] accel/qda: Add GEM_CREATE and GEM_MMAP_OFFSET IOCTLs Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 11/15] accel/qda: Add PRIME DMA-BUF import support Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 12/15] accel/qda: Add FastRPC invocation support Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 13/15] accel/qda: Add DSP process creation and release Ekansh Gupta
2026-08-17 4:47 ` [PATCH v2 14/15] accel/qda: Add remote memory mapping to DSP address space Ekansh Gupta
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox