From: Gyeyoung Baek <gye976@gmail.com>
To: "Alessio Belle" <alessio.belle@imgtec.com>,
"Luigi Santivetti" <luigi.santivetti@imgtec.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Christian König" <christian.koenig@amd.com>,
"Philipp Stanner" <phasta@kernel.org>,
"Donald Robson" <donald.robson@imgtec.com>,
"Sarah Walker" <sarah.walker@imgtec.com>,
"Matt Coster" <opensource@mtcoster.net>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Randy Dunlap" <rdunlap@infradead.org>
Cc: imagination@lists.freedesktop.org,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
linux-media@vger.kernel.org, linaro-mm-sig@lists.linaro.org,
linux-doc@vger.kernel.org, Gyeyoung Baek <gye976@gmail.com>
Subject: [PATCH v2 6/6] drm/imagination: Add MAP_SPARSE to VM_BIND
Date: Sun, 08 Nov 2026 14:16:28 +0900 [thread overview]
Message-ID: <20261108-pvr-vm-bind-v2-6-730103d2cc9a@gmail.com> (raw)
In-Reply-To: <20261108-pvr-vm-bind-v2-0-730103d2cc9a@gmail.com>
Vulkan sparse residency requires that an access to an unbound part of a
sparse resource does not fault, so a range has to be mappable without
naming any buffer object.
Add DRM_PVR_VM_BIND_OP_MAP_SPARSE, which maps one dummy object over and
over across the range.
To save memory, one dummy is shared by all sparse mappings, but per file
rather than per device: writes to a sparse range land in the dummy, so
per file keeps clients isolated from each other.
The dummy is 2MiB, so pvr_mmu_map() runs once per 2MiB, but the mapping
itself is still done per page.
Signed-off-by: Gyeyoung Baek <gye976@gmail.com>
---
drivers/gpu/drm/imagination/pvr_device.h | 10 ++++
drivers/gpu/drm/imagination/pvr_drv.c | 63 +++++++++++++++++++++++--
drivers/gpu/drm/imagination/pvr_vm.c | 81 +++++++++++++++++++++++++++++---
drivers/gpu/drm/imagination/pvr_vm.h | 8 ++++
include/uapi/drm/pvr_drm.h | 21 +++++++--
5 files changed, 169 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/imagination/pvr_device.h b/drivers/gpu/drm/imagination/pvr_device.h
index c246122b4ca..c8a7a949fdc 100644
--- a/drivers/gpu/drm/imagination/pvr_device.h
+++ b/drivers/gpu/drm/imagination/pvr_device.h
@@ -380,6 +380,16 @@ struct pvr_file {
*/
struct xarray hwrt_handles;
+ /**
+ * @sparse_dummy_bo: Object the file's sparse mappings repeat. Pins its
+ * pages, so it is created on first use, under @sparse_dummy_lock. Per
+ * file, so that sparse writes stay out of other clients' view.
+ */
+ struct pvr_gem_object *sparse_dummy_bo;
+
+ /** @sparse_dummy_lock: Serialises the creation of @sparse_dummy_bo. */
+ struct mutex sparse_dummy_lock;
+
/**
* @vm_ctx_handles: Array of VM contexts belonging to this file. Array
* members are of type "struct pvr_vm_context *".
diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c
index c35d437aef7..377fa8d936a 100644
--- a/drivers/gpu/drm/imagination/pvr_drv.c
+++ b/drivers/gpu/drm/imagination/pvr_drv.c
@@ -1137,6 +1137,37 @@ pvr_ioctl_vm_unmap(struct drm_device *drm_dev, void *raw_args,
return err;
}
+/**
+ * pvr_file_get_sparse_dummy() - Get a file's sparse dummy object.
+ * @pvr_file: Target PowerVR file.
+ *
+ * Return: The object, with a reference for the caller, or an ERR_PTR on
+ * failure.
+ */
+static struct pvr_gem_object *
+pvr_file_get_sparse_dummy(struct pvr_file *pvr_file)
+{
+ struct pvr_gem_object *dummy;
+
+ mutex_lock(&pvr_file->sparse_dummy_lock);
+
+ dummy = pvr_file->sparse_dummy_bo;
+ if (!dummy) {
+ dummy = pvr_vm_sparse_dummy_create(pvr_file->pvr_dev);
+ if (IS_ERR(dummy))
+ goto err_unlock;
+
+ pvr_file->sparse_dummy_bo = dummy;
+ }
+
+ pvr_gem_object_get(dummy);
+
+err_unlock:
+ mutex_unlock(&pvr_file->sparse_dummy_lock);
+
+ return dummy;
+}
+
/**
* pvr_vm_bind_desc_from_uapi() - Translate a userspace bind operation.
* @desc: Description to fill in.
@@ -1150,6 +1181,8 @@ pvr_vm_bind_desc_from_uapi(struct pvr_vm_bind_desc *desc,
struct pvr_file *pvr_file,
const struct drm_pvr_vm_bind_op *uapi_op)
{
+ const bool sparse = uapi_op->flags & DRM_PVR_VM_BIND_OP_MAP_SPARSE;
+
if (uapi_op->flags & ~DRM_PVR_VM_BIND_OP_FLAGS_MASK)
return -EINVAL;
@@ -1157,19 +1190,35 @@ pvr_vm_bind_desc_from_uapi(struct pvr_vm_bind_desc *desc,
.offset = uapi_op->offset,
.device_addr = uapi_op->device_addr,
.size = uapi_op->size,
+ .sparse = sparse,
};
switch (uapi_op->flags & DRM_PVR_VM_BIND_OP_TYPE_MASK) {
case DRM_PVR_VM_BIND_OP_TYPE_MAP:
desc->type = PVR_VM_BIND_TYPE_MAP;
- desc->obj = pvr_gem_object_from_handle(pvr_file, uapi_op->handle);
- if (!desc->obj)
- return -ENOENT;
+
+ if (sparse) {
+ struct pvr_gem_object *dummy;
+
+ if (uapi_op->handle || uapi_op->offset)
+ return -EINVAL;
+
+ dummy = pvr_file_get_sparse_dummy(pvr_file);
+ if (IS_ERR(dummy))
+ return PTR_ERR(dummy);
+
+ desc->obj = dummy;
+ } else {
+ desc->obj = pvr_gem_object_from_handle(pvr_file,
+ uapi_op->handle);
+ if (!desc->obj)
+ return -ENOENT;
+ }
return 0;
case DRM_PVR_VM_BIND_OP_TYPE_UNMAP:
- if (uapi_op->handle || uapi_op->offset)
+ if (uapi_op->handle || uapi_op->offset || sparse)
return -EINVAL;
desc->type = PVR_VM_BIND_TYPE_UNMAP;
@@ -1615,6 +1664,8 @@ pvr_drm_driver_open(struct drm_device *drm_dev, struct drm_file *file)
INIT_LIST_HEAD(&pvr_file->contexts);
+ mutex_init(&pvr_file->sparse_dummy_lock);
+
xa_init_flags(&pvr_file->ctx_handles, XA_FLAGS_ALLOC1);
xa_init_flags(&pvr_file->free_list_handles, XA_FLAGS_ALLOC1);
xa_init_flags(&pvr_file->hwrt_handles, XA_FLAGS_ALLOC1);
@@ -1653,6 +1704,10 @@ pvr_drm_driver_postclose(__always_unused struct drm_device *drm_dev,
pvr_destroy_hwrt_datasets_for_file(pvr_file);
pvr_destroy_vm_contexts_for_file(pvr_file);
+ if (pvr_file->sparse_dummy_bo)
+ pvr_gem_object_put(pvr_file->sparse_dummy_bo);
+ mutex_destroy(&pvr_file->sparse_dummy_lock);
+
kfree(pvr_file);
file->driver_priv = NULL;
}
diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c
index 4e58ea8c50c..0c22c63e498 100644
--- a/drivers/gpu/drm/imagination/pvr_vm.c
+++ b/drivers/gpu/drm/imagination/pvr_vm.c
@@ -22,8 +22,10 @@
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/kref.h>
+#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/sched.h>
+#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/stddef.h>
#include <linux/workqueue.h>
@@ -188,6 +190,9 @@ struct pvr_vm_bind_op {
*/
struct pvr_vm_gpuva *next_va;
+ /** @sparse: See &pvr_vm_bind_desc.sparse. */
+ bool sparse;
+
/** @offset: Offset into @pvr_obj to begin mapping from. */
u64 offset;
@@ -280,11 +285,19 @@ pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op,
if (!pvr_device_addr_and_size_are_valid(vm_ctx, desc->device_addr,
desc->size) ||
- desc->offset & ~PAGE_MASK || desc->size & ~PAGE_MASK ||
- desc->offset >= pvr_obj_size || offset_plus_size > pvr_obj_size)
+ desc->offset & ~PAGE_MASK || desc->size & ~PAGE_MASK)
+ return -EINVAL;
+
+ /*
+ * A sparse mapping repeats @obj over a range wider than it, so only
+ * ordinary mappings must fit within @obj.
+ */
+ if (!desc->sparse &&
+ (desc->offset >= pvr_obj_size || offset_plus_size > pvr_obj_size))
return -EINVAL;
bind_op->type = PVR_VM_BIND_TYPE_MAP;
+ bind_op->sparse = desc->sparse;
bind_op->gpuvm_bo = drm_gpuvm_bo_create(&vm_ctx->gpuvm_mgr, obj);
if (!bind_op->gpuvm_bo)
@@ -373,6 +386,58 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op,
return err;
}
+/**
+ * pvr_vm_sparse_dummy_create() - Create the object sparse mappings repeat.
+ * @pvr_dev: Target PowerVR device.
+ *
+ * It covers a whole level 0 page table, so that each chunk mapped by
+ * pvr_vm_mmu_map_sparse() fills one.
+ *
+ * Return: The new object, or an ERR_PTR on failure.
+ */
+struct pvr_gem_object *pvr_vm_sparse_dummy_create(struct pvr_device *pvr_dev)
+{
+ return pvr_gem_object_create(pvr_dev, SZ_2M,
+ DRM_PVR_BO_BYPASS_DEVICE_CACHE);
+}
+
+/**
+ * pvr_vm_mmu_map_sparse() - Map a dummy object over and over across a range.
+ * @mmu_op_ctx: Target MMU op context, created for @dummy.
+ * @dummy: Object to repeat.
+ * @device_addr: Virtual device address at the start of the range.
+ * @size: Size of the range.
+ *
+ * Maps @dummy repeatedly, in chunks aligned to its size, which must be a power
+ * of two.
+ *
+ * Return: 0 on success, or a negative error code with nothing left mapped.
+ */
+static int
+pvr_vm_mmu_map_sparse(struct pvr_mmu_op_context *mmu_op_ctx,
+ struct pvr_gem_object *dummy, u64 device_addr, u64 size)
+{
+ const u64 dummy_size = pvr_gem_object_size(dummy);
+ u64 mapped = 0;
+ int err;
+
+ while (mapped < size) {
+ const u64 addr = device_addr + mapped;
+ const u64 chunk = min(size - mapped,
+ dummy_size - (addr & (dummy_size - 1)));
+
+ err = pvr_mmu_map(mmu_op_ctx, chunk, dummy->flags, addr);
+ if (err) {
+ pvr_mmu_unmap(mmu_op_ctx, device_addr, mapped);
+ return err;
+ }
+
+ mapped += chunk;
+ }
+
+ return 0;
+}
+
/**
* pvr_vm_gpuva_map() - Insert a mapping into a memory context.
* @op: gpuva op containing the remap details.
@@ -383,7 +448,7 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op,
*
* Return:
* * 0 on success, or
- * * Any error returned by pvr_mmu_map().
+ * * Any error returned by pvr_mmu_map() or pvr_vm_mmu_map_sparse().
*/
static int
pvr_vm_gpuva_map(struct drm_gpuva_op *op, void *op_ctx)
@@ -395,8 +460,12 @@ pvr_vm_gpuva_map(struct drm_gpuva_op *op, void *op_ctx)
if ((op->map.gem.offset | op->map.va.range) & ~PVR_DEVICE_PAGE_MASK)
return -EINVAL;
- err = pvr_mmu_map(ctx->mmu_op_ctx, op->map.va.range, pvr_gem->flags,
- op->map.va.addr);
+ if (ctx->sparse)
+ err = pvr_vm_mmu_map_sparse(ctx->mmu_op_ctx, pvr_gem,
+ op->map.va.addr, op->map.va.range);
+ else
+ err = pvr_mmu_map(ctx->mmu_op_ctx, op->map.va.range,
+ pvr_gem->flags, op->map.va.addr);
if (err)
return err;
@@ -741,7 +810,7 @@ pvr_vm_bind_op_init(struct pvr_vm_bind_op *bind_op,
return pvr_vm_bind_op_map_init(bind_op, vm_ctx, desc);
case PVR_VM_BIND_TYPE_UNMAP:
- if (WARN_ON(desc->obj))
+ if (WARN_ON(desc->obj || desc->sparse))
return -EINVAL;
return pvr_vm_bind_op_unmap_init(bind_op, vm_ctx, desc);
diff --git a/drivers/gpu/drm/imagination/pvr_vm.h b/drivers/gpu/drm/imagination/pvr_vm.h
index df74b405960..68d5ffa5e88 100644
--- a/drivers/gpu/drm/imagination/pvr_vm.h
+++ b/drivers/gpu/drm/imagination/pvr_vm.h
@@ -80,6 +80,12 @@ struct pvr_vm_bind_desc {
/** @size: Size of the range. */
u64 size;
+
+ /**
+ * @sparse: Repeat @obj over the range rather than covering it once.
+ * Map only.
+ */
+ bool sparse;
};
int pvr_vm_bind(struct pvr_vm_context *vm_ctx,
@@ -93,6 +99,8 @@ void pvr_vm_unmap_all(struct pvr_vm_context *vm_ctx);
bool pvr_vm_context_is_unusable(struct pvr_vm_context *vm_ctx);
+struct pvr_gem_object *pvr_vm_sparse_dummy_create(struct pvr_device *pvr_dev);
+
dma_addr_t pvr_vm_get_page_table_root_addr(struct pvr_vm_context *vm_ctx);
struct dma_resv *pvr_vm_get_dma_resv(struct pvr_vm_context *vm_ctx);
diff --git a/include/uapi/drm/pvr_drm.h b/include/uapi/drm/pvr_drm.h
index 57c22f5f215..a7c2801cc70 100644
--- a/include/uapi/drm/pvr_drm.h
+++ b/include/uapi/drm/pvr_drm.h
@@ -1333,7 +1333,17 @@ struct drm_pvr_ioctl_submit_jobs_args {
/**
* DOC: Flags for VM_BIND operations.
*
- * Set exactly one operation type in &drm_pvr_vm_bind_op.flags.
+ * Set exactly one operation type in &drm_pvr_vm_bind_op.flags, optionally
+ * ORed with modifiers.
+ *
+ * .. c:macro:: DRM_PVR_VM_BIND_OP_MAP_SPARSE
+ *
+ * Sparsely map a range without naming a buffer object. Only valid with
+ * %DRM_PVR_VM_BIND_OP_TYPE_MAP.
+ *
+ * GPU accesses to the range do not fault. Nothing else is guaranteed:
+ * reads return undefined values, and writes may become visible through
+ * other sparse mappings.
*
* .. c:macro:: DRM_PVR_VM_BIND_OP_TYPE_MAP
*
@@ -1349,11 +1359,14 @@ struct drm_pvr_ioctl_submit_jobs_args {
*
* Mask used to extract the operation type.
*/
+#define DRM_PVR_VM_BIND_OP_MAP_SPARSE _BITUL(0)
+
#define DRM_PVR_VM_BIND_OP_TYPE_MAP (0u << 28)
#define DRM_PVR_VM_BIND_OP_TYPE_UNMAP (1u << 28)
#define DRM_PVR_VM_BIND_OP_TYPE_MASK (0xfu << 28)
-#define DRM_PVR_VM_BIND_OP_FLAGS_MASK DRM_PVR_VM_BIND_OP_TYPE_MASK
+#define DRM_PVR_VM_BIND_OP_FLAGS_MASK \
+ (DRM_PVR_VM_BIND_OP_MAP_SPARSE | DRM_PVR_VM_BIND_OP_TYPE_MASK)
/**
* struct drm_pvr_vm_bind_op - A single VM bind operation.
@@ -1366,13 +1379,13 @@ struct drm_pvr_vm_bind_op {
* @handle: [IN] Handle of the target buffer object.
*
* Must be a valid handle returned by %DRM_IOCTL_PVR_CREATE_BO for map
- * operations. MBZ for unmap operations.
+ * operations. MBZ for unmap and sparse map operations.
*/
__u32 handle;
/**
* @offset: [IN] Offset into the target buffer object from which to
- * begin the mapping. MBZ for unmap operations.
+ * begin the mapping. MBZ for unmap and sparse map operations.
*/
__u64 offset;
--
2.43.0
prev parent reply other threads:[~2026-11-08 5:17 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-08 5:16 [PATCH v2 0/6] drm/imagination: Add async VM_BIND with sparse mappings Gyeyoung Baek
2026-11-08 5:16 ` [PATCH v2 1/6] drm/imagination: Switch the GPUVM to immediate mode Gyeyoung Baek
2026-11-08 5:16 ` [PATCH v2 2/6] drm/imagination: Stop locking reservations around map and unmap Gyeyoung Baek
2026-11-08 5:16 ` [PATCH v2 3/6] drm/imagination: Route map and unmap through pvr_vm_bind() Gyeyoung Baek
2026-11-08 5:16 ` [PATCH v2 4/6] drm/imagination: Add VM_BIND ioctl Gyeyoung Baek
2026-11-08 5:16 ` [PATCH v2 5/6] drm/imagination: Add asynchronous VM_BIND Gyeyoung Baek
2026-11-08 5:16 ` Gyeyoung Baek [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261108-pvr-vm-bind-v2-6-730103d2cc9a@gmail.com \
--to=gye976@gmail.com \
--cc=airlied@gmail.com \
--cc=alessio.belle@imgtec.com \
--cc=christian.koenig@amd.com \
--cc=corbet@lwn.net \
--cc=donald.robson@imgtec.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=imagination@lists.freedesktop.org \
--cc=linaro-mm-sig@lists.linaro.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=luigi.santivetti@imgtec.com \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=opensource@mtcoster.net \
--cc=phasta@kernel.org \
--cc=rdunlap@infradead.org \
--cc=sarah.walker@imgtec.com \
--cc=simona@ffwll.ch \
--cc=skhan@linuxfoundation.org \
--cc=sumit.semwal@linaro.org \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®