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: 10+ 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:38 ` sashiko-bot
2026-11-08 5:16 ` [PATCH v2 4/6] drm/imagination: Add VM_BIND ioctl Gyeyoung Baek
2026-11-08 5:43 ` sashiko-bot
2026-11-08 5:16 ` [PATCH v2 5/6] drm/imagination: Add asynchronous VM_BIND Gyeyoung Baek
2026-11-08 5:47 ` sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.