* [PATCH v6 0/4] Support sparse mappings in Panthor
@ 2026-04-03 19:27 Adrián Larumbe
2026-04-03 19:27 ` [PATCH v6 1/4] drm/panthor: Expose GPU page sizes to UM Adrián Larumbe
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Adrián Larumbe @ 2026-04-03 19:27 UTC (permalink / raw)
To: linux-kernel
Cc: dri-devel, Steven Price, Boris Brezillon, kernel,
Adrián Larumbe
This patch series implements sparse mappings in Panthor. Owing to the lack of HW MMU
features for sparse page table entries, this had to be implemented using a dummy object
over which sparse mappings requested over VM_BIND are mapped cyclically.
To that end, a new VM_BIND flag was added in the driver's uAPI.
The end goal of this patch series is to improve support of Vulkan sparse
resources. At the moment, to implement this feature on Mali hardware, Vulkan
sparse map is implemented by mapping the specified region to a "dummy bo" so
that the accesses do not fault. A newly created sparse resource starts off
unmapped, and therefore also has to be mapped to the "dummy bo". This "dummy
bo" is small (a page size) in comparison to the sizes of va ranges that we might
want to map to it, and a large number of vm_bind ops can be necessary. For
example, if the user were to create a 100e6-byte sparse resident resource, we'd
have to poke VM_BIND with ceil(100e6/0x1000)=24415 map operations.
The new VM_BIND sparse mapping feature addresses this particular inefficiency by
letting us implement a single Vulkan sparse map operation and sparse resident
resource initialization with just one map operation.
Link to the conversation for the previous patch series revision at:
https://lore.kernel.org/dri-devel/20260313150956.1618635-1-adrian.larumbe@collabora.com/
Potential improvements in a v7 iteration:
- Introduce lifetime management for sparse mappings' dummy backing pages.
- Add Panfrost support for reporting available page sizes through query param ioctl.
Changes in v6:
- Moved all the GPUVM core code into the driver backend.
- Discarded commits that touch on the gpuvm core too.
- Redesigned the uAPI so that no repeat range or user BO is supplied for sparse mappings.
- Replaced user-supplied BO with a kernel-allocated array of raw pages.
Changes in v5:
- Minor fixes to drm_gpuvm.c.
- Add panthor MMU page sizes device queriable param.
- Add helper to make sure unmaps of repeated regions are correct.
- Some fixes to Panthor's repeat mappings implementation.
- Lump arguments to panthor_vm_prepare_map_op_ctx into a single struct.
Changes in v4:
- Fixed the warnings reported by the kernel test robot.
https://lore.kernel.org/oe-kbuild-all/202507041635.WyDu3TQ1-lkp@intel.com/
- Fixed the warnings reported by the CI.
https://patchwork.freedesktop.org/series/151264/
No changes in v3.
Changes in v2:
- Make panthor use this stuff.
- Make it possible to express a repeated mappina of any suitably sized
and aligned range of a BO, rather than strictly the page size -sized
prefix, generalizing the API. Rename DRM_GPUVA_SINGLE_PAGE to
DRM_GPUVA_REPEAT.
- Clean up parts of drm/gpuvm affected by these changes.
Adrián Larumbe (4):
drm/panthor: Expose GPU page sizes to UM
drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx
drm/panthor: Support sparse mappings
drm/panthor: Bump the driver version to 1.9
drivers/gpu/drm/panthor/panthor_device.h | 3 +
drivers/gpu/drm/panthor/panthor_drv.c | 10 +
drivers/gpu/drm/panthor/panthor_mmu.c | 299 ++++++++++++++++-------
include/uapi/drm/panthor_drm.h | 26 +-
4 files changed, 253 insertions(+), 85 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v6 1/4] drm/panthor: Expose GPU page sizes to UM 2026-04-03 19:27 [PATCH v6 0/4] Support sparse mappings in Panthor Adrián Larumbe @ 2026-04-03 19:27 ` Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx Adrián Larumbe ` (2 subsequent siblings) 3 siblings, 0 replies; 7+ messages in thread From: Adrián Larumbe @ 2026-04-03 19:27 UTC (permalink / raw) To: linux-kernel Cc: dri-devel, Steven Price, Boris Brezillon, kernel, Adrián Larumbe, Liviu Dudau, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Daniel Almeida, Alice Ryhl In future commits that will implement repeated mappings, only repeat values multiple of GPU page sizes will be tolerated. That means these values must be made known to UM. Do it through a queriable GPU info value. Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> --- drivers/gpu/drm/panthor/panthor_device.h | 3 +++ drivers/gpu/drm/panthor/panthor_drv.c | 8 ++++++++ drivers/gpu/drm/panthor/panthor_mmu.c | 9 ++++++++- include/uapi/drm/panthor_drm.h | 13 +++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_device.h b/drivers/gpu/drm/panthor/panthor_device.h index 5cba272f9b4d..d856a4fe1d61 100644 --- a/drivers/gpu/drm/panthor/panthor_device.h +++ b/drivers/gpu/drm/panthor/panthor_device.h @@ -158,6 +158,9 @@ struct panthor_device { /** @csif_info: Command stream interface information. */ struct drm_panthor_csif_info csif_info; + /** @mmu_info: MMU info */ + struct drm_panthor_mmu_info mmu_info; + /** @hw: GPU-specific data. */ struct panthor_hw *hw; diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index 73fc983dc9b4..a8090bc4e33c 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -175,6 +175,7 @@ panthor_get_uobj_array(const struct drm_panthor_obj_array *in, u32 min_stride, _Generic(_obj_name, \ PANTHOR_UOBJ_DECL(struct drm_panthor_gpu_info, tiler_present), \ PANTHOR_UOBJ_DECL(struct drm_panthor_csif_info, pad), \ + PANTHOR_UOBJ_DECL(struct drm_panthor_mmu_info, page_size_bitmap), \ PANTHOR_UOBJ_DECL(struct drm_panthor_timestamp_info, current_timestamp), \ PANTHOR_UOBJ_DECL(struct drm_panthor_group_priorities_info, pad), \ PANTHOR_UOBJ_DECL(struct drm_panthor_sync_op, timeline_value), \ @@ -946,6 +947,10 @@ static int panthor_ioctl_dev_query(struct drm_device *ddev, void *data, struct d args->size = sizeof(ptdev->csif_info); return 0; + case DRM_PANTHOR_DEV_QUERY_MMU_INFO: + args->size = sizeof(ptdev->mmu_info); + return 0; + case DRM_PANTHOR_DEV_QUERY_TIMESTAMP_INFO: args->size = sizeof(timestamp_info); return 0; @@ -966,6 +971,9 @@ static int panthor_ioctl_dev_query(struct drm_device *ddev, void *data, struct d case DRM_PANTHOR_DEV_QUERY_CSIF_INFO: return PANTHOR_UOBJ_SET(args->pointer, args->size, ptdev->csif_info); + case DRM_PANTHOR_DEV_QUERY_MMU_INFO: + return PANTHOR_UOBJ_SET(args->pointer, args->size, ptdev->mmu_info); + case DRM_PANTHOR_DEV_QUERY_TIMESTAMP_INFO: ret = copy_struct_from_user(×tamp_info, sizeof(timestamp_info), diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 2b96359d3b94..880c8c4cca17 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -2763,7 +2763,7 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu, refcount_set(&vm->as.active_cnt, 0); pgtbl_cfg = (struct io_pgtable_cfg) { - .pgsize_bitmap = SZ_4K | SZ_2M, + .pgsize_bitmap = ptdev->mmu_info.page_size_bitmap, .ias = va_bits, .oas = pa_bits, .coherent_walk = ptdev->coherent, @@ -3208,6 +3208,11 @@ static void panthor_mmu_release_wq(struct drm_device *ddev, void *res) destroy_workqueue(res); } +static void panthor_mmu_info_init(struct panthor_device *ptdev) +{ + ptdev->mmu_info.page_size_bitmap = SZ_4K | SZ_2M; +} + /** * panthor_mmu_init() - Initialize the MMU logic. * @ptdev: Device. @@ -3220,6 +3225,8 @@ int panthor_mmu_init(struct panthor_device *ptdev) struct panthor_mmu *mmu; int ret, irq; + panthor_mmu_info_init(ptdev); + mmu = drmm_kzalloc(&ptdev->base, sizeof(*mmu), GFP_KERNEL); if (!mmu) return -ENOMEM; diff --git a/include/uapi/drm/panthor_drm.h b/include/uapi/drm/panthor_drm.h index 0e455d91e77d..dc2704fc2829 100644 --- a/include/uapi/drm/panthor_drm.h +++ b/include/uapi/drm/panthor_drm.h @@ -246,6 +246,9 @@ enum drm_panthor_dev_query_type { /** @DRM_PANTHOR_DEV_QUERY_CSIF_INFO: Query command-stream interface information. */ DRM_PANTHOR_DEV_QUERY_CSIF_INFO, + /** @DRM_PANTHOR_DEV_QUERY_MMU_INFO: Query MMU information. */ + DRM_PANTHOR_DEV_QUERY_MMU_INFO, + /** @DRM_PANTHOR_DEV_QUERY_TIMESTAMP_INFO: Query timestamp information. */ DRM_PANTHOR_DEV_QUERY_TIMESTAMP_INFO, @@ -487,6 +490,16 @@ struct drm_panthor_timestamp_info { __u64 cpu_timestamp_nsec; }; +/** + * struct drm_panthor_mmu_info - MMU information + * + * Structure grouping all queryable information relating to the MMU. + */ +struct drm_panthor_mmu_info { + /** @page_size_bitmap: Allowed page sizes */ + __u64 page_size_bitmap; +}; + /** * struct drm_panthor_group_priorities_info - Group priorities information * -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx 2026-04-03 19:27 [PATCH v6 0/4] Support sparse mappings in Panthor Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 1/4] drm/panthor: Expose GPU page sizes to UM Adrián Larumbe @ 2026-04-03 19:27 ` Adrián Larumbe 2026-04-07 10:44 ` Liviu Dudau 2026-04-03 19:27 ` [PATCH v6 3/4] drm/panthor: Support sparse mappings Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 4/4] drm/panthor: Bump the driver version to 1.9 Adrián Larumbe 3 siblings, 1 reply; 7+ messages in thread From: Adrián Larumbe @ 2026-04-03 19:27 UTC (permalink / raw) To: linux-kernel Cc: dri-devel, Steven Price, Boris Brezillon, kernel, Adrián Larumbe, Liviu Dudau, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Instead of passing its constituent elements, pass the whole struct to simplify the function prototype. Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> --- drivers/gpu/drm/panthor/panthor_mmu.c | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 880c8c4cca17..10da2f021d9d 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -1275,9 +1275,7 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx) static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, struct panthor_vm *vm, struct panthor_gem_object *bo, - u64 offset, - u64 size, u64 va, - u32 flags) + const struct drm_panthor_vm_bind_op *op) { struct drm_gpuvm_bo *preallocated_vm_bo; struct sg_table *sgt = NULL; @@ -1286,12 +1284,12 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, if (!bo) return -EINVAL; - if ((flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || - (flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) + if ((op->flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || + (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) return -EINVAL; /* Make sure the VA and size are in-bounds. */ - if (size > bo->base.size || offset > bo->base.size - size) + if (op->size > bo->base.size || op->bo_offset > bo->base.size - op->size) return -EINVAL; /* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */ @@ -1299,7 +1297,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm)) return -EINVAL; - panthor_vm_init_op_ctx(op_ctx, size, va, flags); + panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags); ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx); if (ret) @@ -1328,7 +1326,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, } op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); - op_ctx->map.bo_offset = offset; + op_ctx->map.bo_offset = op->bo_offset; ret = panthor_vm_op_ctx_prealloc_pts(op_ctx); if (ret) @@ -2843,10 +2841,7 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file, gem = drm_gem_object_lookup(file, op->bo_handle); ret = panthor_vm_prepare_map_op_ctx(op_ctx, vm, gem ? to_panthor_bo(gem) : NULL, - op->bo_offset, - op->size, - op->va, - op->flags); + op); drm_gem_object_put(gem); return ret; @@ -3042,10 +3037,18 @@ int panthor_vm_bind_exec_sync_op(struct drm_file *file, int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo, u64 offset, u64 size, u64 va, u32 flags) { + struct drm_panthor_vm_bind_op op = {0}; struct panthor_vm_op_ctx op_ctx; int ret; - ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, offset, size, va, flags); + op = (struct drm_panthor_vm_bind_op){ + .bo_offset = offset, + .size = size, + .va = va, + .flags = flags, + }; + + ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, &op); if (ret) return ret; -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx 2026-04-03 19:27 ` [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx Adrián Larumbe @ 2026-04-07 10:44 ` Liviu Dudau 0 siblings, 0 replies; 7+ messages in thread From: Liviu Dudau @ 2026-04-07 10:44 UTC (permalink / raw) To: Adrián Larumbe Cc: linux-kernel, dri-devel, Steven Price, Boris Brezillon, kernel, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter On Fri, Apr 03, 2026 at 08:27:27PM +0100, Adrián Larumbe wrote: > Instead of passing its constituent elements, pass the whole struct to > simplify the function prototype. > > Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> > --- > drivers/gpu/drm/panthor/panthor_mmu.c | 29 +++++++++++++++------------ > 1 file changed, 16 insertions(+), 13 deletions(-) > > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c > index 880c8c4cca17..10da2f021d9d 100644 > --- a/drivers/gpu/drm/panthor/panthor_mmu.c > +++ b/drivers/gpu/drm/panthor/panthor_mmu.c > @@ -1275,9 +1275,7 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx) > static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > struct panthor_vm *vm, > struct panthor_gem_object *bo, > - u64 offset, > - u64 size, u64 va, > - u32 flags) > + const struct drm_panthor_vm_bind_op *op) > { > struct drm_gpuvm_bo *preallocated_vm_bo; > struct sg_table *sgt = NULL; > @@ -1286,12 +1284,12 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > if (!bo) > return -EINVAL; > > - if ((flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || > - (flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) > + if ((op->flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || > + (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) > return -EINVAL; > > /* Make sure the VA and size are in-bounds. */ > - if (size > bo->base.size || offset > bo->base.size - size) > + if (op->size > bo->base.size || op->bo_offset > bo->base.size - op->size) > return -EINVAL; > > /* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */ > @@ -1299,7 +1297,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm)) > return -EINVAL; > > - panthor_vm_init_op_ctx(op_ctx, size, va, flags); > + panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags); > > ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx); > if (ret) > @@ -1328,7 +1326,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > } > > op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); > - op_ctx->map.bo_offset = offset; > + op_ctx->map.bo_offset = op->bo_offset; > > ret = panthor_vm_op_ctx_prealloc_pts(op_ctx); > if (ret) > @@ -2843,10 +2841,7 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file, > gem = drm_gem_object_lookup(file, op->bo_handle); > ret = panthor_vm_prepare_map_op_ctx(op_ctx, vm, > gem ? to_panthor_bo(gem) : NULL, > - op->bo_offset, > - op->size, > - op->va, > - op->flags); > + op); > drm_gem_object_put(gem); > return ret; > > @@ -3042,10 +3037,18 @@ int panthor_vm_bind_exec_sync_op(struct drm_file *file, > int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo, > u64 offset, u64 size, u64 va, u32 flags) > { > + struct drm_panthor_vm_bind_op op = {0}; > struct panthor_vm_op_ctx op_ctx; > int ret; > > - ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, offset, size, va, flags); > + op = (struct drm_panthor_vm_bind_op){ > + .bo_offset = offset, > + .size = size, > + .va = va, > + .flags = flags, > + }; You can merge the assignment with the declaration, at least GCC is OK with it. Reviewed-by: Liviu Dudau <liviu.dudau@arm.com> Best regards, Liviu > + > + ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, &op); > if (ret) > return ret; > > -- > 2.53.0 > -- ==================== | I would like to | | fix the world, | | but they're not | | giving me the | \ source code! / --------------- ¯\_(ツ)_/¯ ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v6 3/4] drm/panthor: Support sparse mappings 2026-04-03 19:27 [PATCH v6 0/4] Support sparse mappings in Panthor Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 1/4] drm/panthor: Expose GPU page sizes to UM Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx Adrián Larumbe @ 2026-04-03 19:27 ` Adrián Larumbe 2026-04-07 12:34 ` Boris Brezillon 2026-04-03 19:27 ` [PATCH v6 4/4] drm/panthor: Bump the driver version to 1.9 Adrián Larumbe 3 siblings, 1 reply; 7+ messages in thread From: Adrián Larumbe @ 2026-04-03 19:27 UTC (permalink / raw) To: linux-kernel Cc: dri-devel, Steven Price, Boris Brezillon, kernel, Adrián Larumbe, Liviu Dudau, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Daniel Almeida, Alice Ryhl Allow UM to bind sparsely populated memory regions by cyclically mapping virtual ranges over a set of zero-initialised dummy pages. This alternative is preferable to the old method of handling sparseness in the UMD, because it relied on the creation of a buffer object to the same end, despite the fact Vulkan sparse resources don't need to be backed by a driver BO. Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> --- drivers/gpu/drm/panthor/panthor_mmu.c | 267 +++++++++++++++++++------- include/uapi/drm/panthor_drm.h | 13 +- 2 files changed, 206 insertions(+), 74 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 10da2f021d9d..e73657aebfb0 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -104,6 +104,11 @@ struct panthor_mmu { /** @vm.wq: Workqueue used for the VM_BIND queues. */ struct workqueue_struct *wq; } vm; + + struct { + struct page *pages; + struct sg_table *sgt; + } dummy; }; /** @@ -1028,6 +1033,34 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot, return 0; } +static int +panthor_vm_repeated_map_pages(struct panthor_vm *vm, u64 iova, int prot, + struct sg_table *sgt, u64 size) +{ + u64 first_iova = iova; + u64 first_size = size; + int ret; + + /* FIXME: we really need to optimize this at the io_pgtable level. */ + while (size > 0) { + u64 next_size = min(size, sgt->sgl->length); + + ret = panthor_vm_map_pages(vm, iova, prot, + sgt, 0, next_size); + if (ret) + goto err_unmap; + + size -= next_size; + iova += next_size; + } + + return 0; + +err_unmap: + panthor_vm_unmap_pages(vm, first_iova, first_size - size); + return ret; +} + static int flags_to_prot(u32 flags) { int prot = 0; @@ -1270,6 +1303,7 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx) (DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \ DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \ DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \ + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE | \ DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, @@ -1277,74 +1311,87 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, struct panthor_gem_object *bo, const struct drm_panthor_vm_bind_op *op) { + bool is_sparse = op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; struct drm_gpuvm_bo *preallocated_vm_bo; struct sg_table *sgt = NULL; int ret; - if (!bo) - return -EINVAL; - if ((op->flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) return -EINVAL; - /* Make sure the VA and size are in-bounds. */ - if (op->size > bo->base.size || op->bo_offset > bo->base.size - op->size) - return -EINVAL; + if (!is_sparse) { + /* Make sure the VA and size are in-bounds. */ + if (!bo || op->size > bo->base.size || + op->bo_offset > bo->base.size - op->size) + return -EINVAL; + } else { + if (bo || op->bo_handle || op->bo_offset) + return -EINVAL; + } /* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */ - if (bo->exclusive_vm_root_gem && + if (!is_sparse && bo->exclusive_vm_root_gem && bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm)) return -EINVAL; - panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags); + panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags + | ((is_sparse) ? DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC : 0)); ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx); if (ret) goto err_cleanup; - /* Pre-reserve the BO pages, so the map operation doesn't have to - * allocate. - */ - ret = panthor_gem_pin(bo); - if (ret) - goto err_cleanup; + if (!is_sparse) { + /* Pre-reserve the BO pages, so the map operation doesn't have to + * allocate. + */ + ret = panthor_gem_pin(bo); + if (ret) + goto err_cleanup; - drm_gem_object_get(&bo->base); - op_ctx->map.bo = bo; + drm_gem_object_get(&bo->base); + op_ctx->map.bo = bo; - sgt = panthor_gem_get_dev_sgt(bo); - if (IS_ERR(sgt)) { - ret = PTR_ERR(sgt); - goto err_cleanup; - } + sgt = panthor_gem_get_dev_sgt(bo); + if (IS_ERR(sgt)) { + ret = PTR_ERR(sgt); + goto err_cleanup; + } + op_ctx->map.sgt = sgt; - preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base); - if (!preallocated_vm_bo) { - ret = -ENOMEM; - goto err_cleanup; - } + preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base); + if (!preallocated_vm_bo) { + ret = -ENOMEM; + goto err_cleanup; + } - op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); - op_ctx->map.bo_offset = op->bo_offset; + op_ctx->map.vm_bo = + drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); + op_ctx->map.bo_offset = op->bo_offset; + } else { + op_ctx->map.sgt = vm->ptdev->mmu->dummy.sgt; + } ret = panthor_vm_op_ctx_prealloc_pts(op_ctx); if (ret) goto err_cleanup; - /* Insert BO into the extobj list last, when we know nothing can fail. */ - if (bo->base.resv != panthor_vm_resv(vm)) { - dma_resv_lock(panthor_vm_resv(vm), NULL); - drm_gpuvm_bo_extobj_add(op_ctx->map.vm_bo); - dma_resv_unlock(panthor_vm_resv(vm)); - } + if (!is_sparse) { + /* Insert BO into the extobj list last, when we know nothing can fail. */ + if (bo->base.resv != panthor_vm_resv(vm)) { + dma_resv_lock(panthor_vm_resv(vm), NULL); + drm_gpuvm_bo_extobj_add(op_ctx->map.vm_bo); + dma_resv_unlock(panthor_vm_resv(vm)); + } - /* And finally update the BO state. */ - dma_resv_lock(bo->base.resv, NULL); - mutex_lock(&bo->base.gpuva.lock); - panthor_gem_update_reclaim_state_locked(bo, NULL); - mutex_unlock(&bo->base.gpuva.lock); - dma_resv_unlock(bo->base.resv); + /* And finally update the BO state. */ + dma_resv_lock(bo->base.resv, NULL); + mutex_lock(&bo->base.gpuva.lock); + panthor_gem_update_reclaim_state_locked(bo, NULL); + mutex_unlock(&bo->base.gpuva.lock); + dma_resv_unlock(bo->base.resv); + } return 0; @@ -2148,13 +2195,43 @@ static void panthor_vma_init(struct panthor_vma *vma, u32 flags) #define PANTHOR_VM_MAP_FLAGS \ (DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \ DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \ - DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED) + DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \ + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE) + +static int +panthor_vm_map(struct panthor_vm *vm, bool sparse, struct sg_table *sgt, + u64 addr, u64 offset, u64 size, int prot) +{ + int ret; + + if (!size) + return 0; + + if (!sparse) + return panthor_vm_map_pages(vm, addr, prot, sgt, offset, size); + + drm_WARN_ON(&vm->ptdev->base, sgt->sgl->length != SZ_2M); + + if (addr & (SZ_2M - 1)) { + u64 unaligned_size = min(ALIGN(addr, SZ_2M) - addr, size); + + ret = panthor_vm_map_pages(vm, addr, prot, sgt, + 0, unaligned_size); + if (ret) + return ret; + + size -= unaligned_size; + } + + return panthor_vm_repeated_map_pages(vm, addr, prot, sgt, size); +} static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) { struct panthor_vm *vm = priv; struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; struct panthor_vma *vma = panthor_vm_op_ctx_get_vma(op_ctx); + bool is_sparse = op_ctx->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; int ret; if (!vma) @@ -2162,19 +2239,20 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS); - ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags), - op_ctx->map.bo->dmap.sgt, op->map.gem.offset, - op->map.va.range); + ret = panthor_vm_map(vm, is_sparse, op_ctx->map.sgt, op->map.va.addr, + op->map.gem.offset, op->map.va.range, flags_to_prot(vma->flags)); if (ret) { panthor_vm_op_ctx_return_vma(op_ctx, vma); return ret; } drm_gpuva_map(&vm->base, &vma->base, &op->map); - panthor_vma_link(vm, vma, op_ctx->map.vm_bo); - drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); - op_ctx->map.vm_bo = NULL; + if (!is_sparse) { + panthor_vma_link(vm, vma, op_ctx->map.vm_bo); + drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); + op_ctx->map.vm_bo = NULL; + } return 0; } @@ -2194,7 +2272,7 @@ iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr) static void unmap_hugepage_align(const struct drm_gpuva_op_remap *op, - u64 *unmap_start, u64 *unmap_range) + u64 *unmap_start, u64 *unmap_range, bool sparse) { u64 aligned_unmap_start, aligned_unmap_end, unmap_end; @@ -2207,7 +2285,7 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op, */ if (op->prev && aligned_unmap_start < *unmap_start && op->prev->va.addr <= aligned_unmap_start && - iova_mapped_as_huge_page(op->prev, *unmap_start)) { + (sparse || iova_mapped_as_huge_page(op->prev, *unmap_start))) { *unmap_range += *unmap_start - aligned_unmap_start; *unmap_start = aligned_unmap_start; } @@ -2217,7 +2295,7 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op, */ if (op->next && aligned_unmap_end > unmap_end && op->next->va.addr + op->next->va.range >= aligned_unmap_end && - iova_mapped_as_huge_page(op->next, unmap_end - 1)) { + (sparse || iova_mapped_as_huge_page(op->next, *unmap_start))) { *unmap_range += aligned_unmap_end - unmap_end; } } @@ -2229,6 +2307,8 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, struct panthor_vm *vm = priv; struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; struct panthor_vma *prev_vma = NULL, *next_vma = NULL; + struct sg_table *sgt = vm->ptdev->mmu->dummy.sgt; + bool is_sparse = unmap_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; u64 unmap_start, unmap_range; int ret; @@ -2241,7 +2321,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, * unmap region. Calculating the right start address and range for the expanded * unmap operation is the responsibility of the following function. */ - unmap_hugepage_align(&op->remap, &unmap_start, &unmap_range); + unmap_hugepage_align(&op->remap, &unmap_start, &unmap_range, is_sparse); /* If the range changed, we might have to lock a wider region to guarantee * atomicity. panthor_vm_lock_region() bails out early if the new region @@ -2253,14 +2333,15 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, } if (op->remap.prev) { - struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj); u64 offset = op->remap.prev->gem.offset + unmap_start - op->remap.prev->va.addr; u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start; + if (!is_sparse) + sgt = to_panthor_bo(op->remap.prev->gem.obj)->dmap.sgt; + if (!unmap_vma->evicted) { - ret = panthor_vm_map_pages(vm, unmap_start, - flags_to_prot(unmap_vma->flags), - bo->dmap.sgt, offset, size); + ret = panthor_vm_map(vm, is_sparse, sgt, unmap_start, offset, + size, flags_to_prot(unmap_vma->flags)); if (ret) return ret; } @@ -2271,14 +2352,16 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, } if (op->remap.next) { - struct panthor_gem_object *bo = to_panthor_bo(op->remap.next->gem.obj); u64 addr = op->remap.next->va.addr; u64 size = unmap_start + unmap_range - op->remap.next->va.addr; + if (!is_sparse) + sgt = to_panthor_bo(op->remap.next->gem.obj)->dmap.sgt; + if (!unmap_vma->evicted) { - ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags), - bo->dmap.sgt, op->remap.next->gem.offset, - size); + ret = panthor_vm_map(vm, is_sparse, sgt, addr, + op->remap.next->gem.offset, size, + flags_to_prot(unmap_vma->flags)); if (ret) return ret; } @@ -2292,20 +2375,22 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, next_vma ? &next_vma->base : NULL, &op->remap); - if (prev_vma) { - /* panthor_vma_link() transfers the vm_bo ownership to - * the VMA object. Since the vm_bo we're passing is still - * owned by the old mapping which will be released when this - * mapping is destroyed, we need to grab a ref here. - */ - panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo); - } + if (!is_sparse) { + if (prev_vma) { + /* panthor_vma_link() transfers the vm_bo ownership to + * the VMA object. Since the vm_bo we're passing is still + * owned by the old mapping which will be released when this + * mapping is destroyed, we need to grab a ref here. + */ + panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo); + } + + if (next_vma) + panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo); - if (next_vma) { - panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo); + panthor_vma_unlink(unmap_vma); } - panthor_vma_unlink(unmap_vma); return 0; } @@ -2579,11 +2664,9 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op, switch (op_type) { case DRM_PANTHOR_VM_BIND_OP_TYPE_MAP: { - const struct drm_gpuvm_map_req map_req = { + struct drm_gpuvm_map_req map_req = { .map.va.addr = op->va.addr, .map.va.range = op->va.range, - .map.gem.obj = op->map.vm_bo->obj, - .map.gem.offset = op->map.bo_offset, }; if (vm->unusable) { @@ -2591,6 +2674,11 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op, break; } + if (!(op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) { + map_req.map.gem.obj = op->map.vm_bo->obj; + map_req.map.gem.offset = op->map.bo_offset; + } + ret = drm_gpuvm_sm_map(&vm->base, vm, &map_req); break; } @@ -3041,6 +3129,9 @@ int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo struct panthor_vm_op_ctx op_ctx; int ret; + if (drm_WARN_ON(&vm->ptdev->base, flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) + return -EINVAL; + op = (struct drm_panthor_vm_bind_op){ .bo_offset = offset, .size = size, @@ -3204,6 +3295,10 @@ void panthor_mmu_unplug(struct panthor_device *ptdev) } } mutex_unlock(&ptdev->mmu->as.slots_lock); + + sg_free_table(ptdev->mmu->dummy.sgt); + kfree(ptdev->mmu->dummy.sgt); + __free_pages(ptdev->mmu->dummy.pages, get_order(SZ_2M)); } static void panthor_mmu_release_wq(struct drm_device *ddev, void *res) @@ -3269,7 +3364,33 @@ int panthor_mmu_init(struct panthor_device *ptdev) ptdev->gpu_info.mmu_features |= BITS_PER_LONG; } + mmu->dummy.pages = alloc_pages(GFP_KERNEL | __GFP_ZERO, get_order(SZ_2M)); + if (!mmu->dummy.pages) + return -ENOMEM; + + mmu->dummy.sgt = kzalloc_obj(*mmu->dummy.sgt); + if (!mmu->dummy.sgt) { + ret = -ENOMEM; + goto mmu_init_free_pages; + } + + ret = sg_alloc_table(mmu->dummy.sgt, 1, GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto mmu_init_free_sgtable; + } + + sg_set_page(mmu->dummy.sgt->sgl, mmu->dummy.pages, SZ_2M, 0); + sg_dma_address(mmu->dummy.sgt->sgl) = page_to_phys(mmu->dummy.pages); + sg_dma_len(mmu->dummy.sgt->sgl) = SZ_2M; + return drmm_add_action_or_reset(&ptdev->base, panthor_mmu_release_wq, mmu->vm.wq); + +mmu_init_free_sgtable: + kfree(mmu->dummy.sgt); +mmu_init_free_pages: + __free_pages(mmu->dummy.pages, get_order(SZ_2M)); + return ret; } #ifdef CONFIG_DEBUG_FS diff --git a/include/uapi/drm/panthor_drm.h b/include/uapi/drm/panthor_drm.h index dc2704fc2829..9e7197845ea4 100644 --- a/include/uapi/drm/panthor_drm.h +++ b/include/uapi/drm/panthor_drm.h @@ -614,6 +614,18 @@ enum drm_panthor_vm_bind_op_flags { */ DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED = 1 << 2, + /** + * @DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE: Repeat a BO range + * + * Only valid with DRM_PANTHOR_VM_BIND_OP_TYPE_MAP. + * + * When this flag is set, the whole vm_bind range is mapped over a dummy + * object in a cyclic fashion, and all GPU reads from the addresses in the + * range return 0. This flag being set means drm_panthor_vm_bind_op:offset + * and drm_panthor_vm_bind_op::handle must both be set to 0. + */ + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE = 1 << 3, + /** * @DRM_PANTHOR_VM_BIND_OP_TYPE_MASK: Mask used to determine the type of operation. */ @@ -677,7 +689,6 @@ struct drm_panthor_vm_bind_op { * This array shall not be empty for sync-only operations. */ struct drm_panthor_obj_array syncs; - }; /** -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v6 3/4] drm/panthor: Support sparse mappings 2026-04-03 19:27 ` [PATCH v6 3/4] drm/panthor: Support sparse mappings Adrián Larumbe @ 2026-04-07 12:34 ` Boris Brezillon 0 siblings, 0 replies; 7+ messages in thread From: Boris Brezillon @ 2026-04-07 12:34 UTC (permalink / raw) To: Adrián Larumbe Cc: linux-kernel, dri-devel, Steven Price, kernel, Liviu Dudau, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Daniel Almeida, Alice Ryhl On Fri, 3 Apr 2026 20:27:28 +0100 Adrián Larumbe <adrian.larumbe@collabora.com> wrote: > Allow UM to bind sparsely populated memory regions by cyclically mapping > virtual ranges over a set of zero-initialised dummy pages. This alternative > is preferable to the old method of handling sparseness in the UMD, because > it relied on the creation of a buffer object to the same end, despite the > fact Vulkan sparse resources don't need to be backed by a driver BO. > > Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> > --- > drivers/gpu/drm/panthor/panthor_mmu.c | 267 +++++++++++++++++++------- > include/uapi/drm/panthor_drm.h | 13 +- > 2 files changed, 206 insertions(+), 74 deletions(-) > > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c > index 10da2f021d9d..e73657aebfb0 100644 > --- a/drivers/gpu/drm/panthor/panthor_mmu.c > +++ b/drivers/gpu/drm/panthor/panthor_mmu.c > @@ -104,6 +104,11 @@ struct panthor_mmu { > /** @vm.wq: Workqueue used for the VM_BIND queues. */ > struct workqueue_struct *wq; > } vm; > + > + struct { > + struct page *pages; > + struct sg_table *sgt; > + } dummy; > }; > > /** > @@ -1028,6 +1033,34 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot, > return 0; > } > > +static int > +panthor_vm_repeated_map_pages(struct panthor_vm *vm, u64 iova, int prot, > + struct sg_table *sgt, u64 size) s/panthor_vm_repeated_map_pages/panthor_vm_map_sparse/ and you don't have to pass the sgt (can be retrieved from vm->ptdev->mmu->dummy.sgt). > +{ > + u64 first_iova = iova; > + u64 first_size = size; > + int ret; > + > + /* FIXME: we really need to optimize this at the io_pgtable level. */ Maybe turn that into a TODO, since that's not really a bug, and lower down the "we really need to" into "we should probably". > + while (size > 0) { > + u64 next_size = min(size, sgt->sgl->length); > + > + ret = panthor_vm_map_pages(vm, iova, prot, > + sgt, 0, next_size); > + if (ret) > + goto err_unmap; > + > + size -= next_size; > + iova += next_size; > + } > + > + return 0; > + > +err_unmap: > + panthor_vm_unmap_pages(vm, first_iova, first_size - size); > + return ret; > +} > + > static int flags_to_prot(u32 flags) > { > int prot = 0; > @@ -1270,6 +1303,7 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx) > (DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \ > DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \ > DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \ > + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE | \ > DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) > > static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > @@ -1277,74 +1311,87 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx, > struct panthor_gem_object *bo, > const struct drm_panthor_vm_bind_op *op) > { > + bool is_sparse = op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; > struct drm_gpuvm_bo *preallocated_vm_bo; > struct sg_table *sgt = NULL; > int ret; > > - if (!bo) > - return -EINVAL; > - > if ((op->flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) || > (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP) > return -EINVAL; > > - /* Make sure the VA and size are in-bounds. */ > - if (op->size > bo->base.size || op->bo_offset > bo->base.size - op->size) > - return -EINVAL; > + if (!is_sparse) { > + /* Make sure the VA and size are in-bounds. */ > + if (!bo || op->size > bo->base.size || > + op->bo_offset > bo->base.size - op->size) > + return -EINVAL; > + } else { > + if (bo || op->bo_handle || op->bo_offset) The bo != NULL check seems redundant: if bo_handle is zero, the BO will always be NULL. nit: I'd probably also probably combine the else+if into an else-if: } else if (op->bo_handle || op->bo_offset) { return -EINVAL; > + return -EINVAL; > + } > > /* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */ > - if (bo->exclusive_vm_root_gem && > + if (!is_sparse && bo->exclusive_vm_root_gem && Let's go for bo != NULL checks instead: if (bo && bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm)) > bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm)) > return -EINVAL; > > - panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags); > + panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags > + | ((is_sparse) ? DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC : 0)); > > ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx); > if (ret) > goto err_cleanup; > > - /* Pre-reserve the BO pages, so the map operation doesn't have to > - * allocate. > - */ > - ret = panthor_gem_pin(bo); > - if (ret) > - goto err_cleanup; > + if (!is_sparse) { Same here, I'd go for if (bo) { > + /* Pre-reserve the BO pages, so the map operation doesn't have to > + * allocate. > + */ > + ret = panthor_gem_pin(bo); > + if (ret) > + goto err_cleanup; > > - drm_gem_object_get(&bo->base); > - op_ctx->map.bo = bo; > + drm_gem_object_get(&bo->base); > + op_ctx->map.bo = bo; > > - sgt = panthor_gem_get_dev_sgt(bo); > - if (IS_ERR(sgt)) { > - ret = PTR_ERR(sgt); > - goto err_cleanup; > - } > + sgt = panthor_gem_get_dev_sgt(bo); > + if (IS_ERR(sgt)) { > + ret = PTR_ERR(sgt); > + goto err_cleanup; > + } > + op_ctx->map.sgt = sgt; > > - preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base); > - if (!preallocated_vm_bo) { > - ret = -ENOMEM; > - goto err_cleanup; > - } > + preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base); > + if (!preallocated_vm_bo) { > + ret = -ENOMEM; > + goto err_cleanup; > + } > > - op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); > - op_ctx->map.bo_offset = op->bo_offset; > + op_ctx->map.vm_bo = > + drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo); > + op_ctx->map.bo_offset = op->bo_offset; > + } else { > + op_ctx->map.sgt = vm->ptdev->mmu->dummy.sgt; I don't think we need to initialize the sgt field in that case, the dummy page sgt can be retrieved from the vm in panthor_vm_map_sparse(). > + } > > ret = panthor_vm_op_ctx_prealloc_pts(op_ctx); > if (ret) > goto err_cleanup; > > - /* Insert BO into the extobj list last, when we know nothing can fail. */ > - if (bo->base.resv != panthor_vm_resv(vm)) { > - dma_resv_lock(panthor_vm_resv(vm), NULL); > - drm_gpuvm_bo_extobj_add(op_ctx->map.vm_bo); > - dma_resv_unlock(panthor_vm_resv(vm)); > - } > + if (!is_sparse) { > + /* Insert BO into the extobj list last, when we know nothing can fail. */ > + if (bo->base.resv != panthor_vm_resv(vm)) { if (bo && bo->base.resv != panthor_vm_resv(vm)) { > + dma_resv_lock(panthor_vm_resv(vm), NULL); > + drm_gpuvm_bo_extobj_add(op_ctx->map.vm_bo); > + dma_resv_unlock(panthor_vm_resv(vm)); > + } > > - /* And finally update the BO state. */ > - dma_resv_lock(bo->base.resv, NULL); > - mutex_lock(&bo->base.gpuva.lock); > - panthor_gem_update_reclaim_state_locked(bo, NULL); > - mutex_unlock(&bo->base.gpuva.lock); > - dma_resv_unlock(bo->base.resv); > + /* And finally update the BO state. */ > + dma_resv_lock(bo->base.resv, NULL); > + mutex_lock(&bo->base.gpuva.lock); > + panthor_gem_update_reclaim_state_locked(bo, NULL); > + mutex_unlock(&bo->base.gpuva.lock); > + dma_resv_unlock(bo->base.resv); > + } > > return 0; > > @@ -2148,13 +2195,43 @@ static void panthor_vma_init(struct panthor_vma *vma, u32 flags) > #define PANTHOR_VM_MAP_FLAGS \ > (DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \ > DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \ > - DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED) > + DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \ > + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE) > + > +static int > +panthor_vm_map(struct panthor_vm *vm, bool sparse, struct sg_table *sgt, > + u64 addr, u64 offset, u64 size, int prot) > +{ > + int ret; > + > + if (!size) > + return 0; > + > + if (!sparse) > + return panthor_vm_map_pages(vm, addr, prot, sgt, offset, size); > + > + drm_WARN_ON(&vm->ptdev->base, sgt->sgl->length != SZ_2M); > + > + if (addr & (SZ_2M - 1)) { > + u64 unaligned_size = min(ALIGN(addr, SZ_2M) - addr, size); > + > + ret = panthor_vm_map_pages(vm, addr, prot, sgt, > + 0, unaligned_size); > + if (ret) > + return ret; > + > + size -= unaligned_size; > + } > + > + return panthor_vm_repeated_map_pages(vm, addr, prot, sgt, size); > +} How about: static int panthor_vm_exec_map_op(struct panthor_vm *vm, const struct panthor_vm_op_ctx *op_ctx, const struct drm_gpuva_op_map *op) { bool is_sparse = op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; if (!op->va.range) return 0; if (op_ctx->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE) { // Do the manual 2M alignment in panthor_vm_map_sparse() return panthor_vm_map_sparse(vm, op->va.addr, flags_to_prot(op_ctx->flags), op->va.range); } return panthor_vm_map_pages(vm, op->va.addr, flags_to_prot(op_ctx->flags), op_ctx->map.sgt, op->map.gem.offset, op->va.range); } > > static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) > { > struct panthor_vm *vm = priv; > struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; > struct panthor_vma *vma = panthor_vm_op_ctx_get_vma(op_ctx); > + bool is_sparse = op_ctx->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; > int ret; > > if (!vma) > @@ -2162,19 +2239,20 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) > > panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS); > > - ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags), > - op_ctx->map.bo->dmap.sgt, op->map.gem.offset, > - op->map.va.range); > + ret = panthor_vm_map(vm, is_sparse, op_ctx->map.sgt, op->map.va.addr, > + op->map.gem.offset, op->map.va.range, flags_to_prot(vma->flags)); > if (ret) { > panthor_vm_op_ctx_return_vma(op_ctx, vma); > return ret; > } > > drm_gpuva_map(&vm->base, &vma->base, &op->map); > - panthor_vma_link(vm, vma, op_ctx->map.vm_bo); > > - drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); > - op_ctx->map.vm_bo = NULL; > + if (!is_sparse) { if (op_ctx->map.vm_bo) { > + panthor_vma_link(vm, vma, op_ctx->map.vm_bo); > + drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo); > + op_ctx->map.vm_bo = NULL; > + } > > return 0; > } > @@ -2194,7 +2272,7 @@ iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr) > > static void > unmap_hugepage_align(const struct drm_gpuva_op_remap *op, > - u64 *unmap_start, u64 *unmap_range) > + u64 *unmap_start, u64 *unmap_range, bool sparse) You can probably get the unmap_vma out of the remap op, and extract the is_sparse info from there. If we decide to keep it as an extra argument, I would call it 'unmapped_vma_is_sparse'. > { > u64 aligned_unmap_start, aligned_unmap_end, unmap_end; > > @@ -2207,7 +2285,7 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op, > */ > if (op->prev && aligned_unmap_start < *unmap_start && > op->prev->va.addr <= aligned_unmap_start && > - iova_mapped_as_huge_page(op->prev, *unmap_start)) { > + (sparse || iova_mapped_as_huge_page(op->prev, *unmap_start))) { > *unmap_range += *unmap_start - aligned_unmap_start; > *unmap_start = aligned_unmap_start; > } > @@ -2217,7 +2295,7 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op, > */ > if (op->next && aligned_unmap_end > unmap_end && > op->next->va.addr + op->next->va.range >= aligned_unmap_end && > - iova_mapped_as_huge_page(op->next, unmap_end - 1)) { > + (sparse || iova_mapped_as_huge_page(op->next, *unmap_start))) { > *unmap_range += aligned_unmap_end - unmap_end; > } > } > @@ -2229,6 +2307,8 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > struct panthor_vm *vm = priv; > struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; > struct panthor_vma *prev_vma = NULL, *next_vma = NULL; > + struct sg_table *sgt = vm->ptdev->mmu->dummy.sgt; > + bool is_sparse = unmap_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE; > u64 unmap_start, unmap_range; > int ret; > > @@ -2241,7 +2321,7 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > * unmap region. Calculating the right start address and range for the expanded > * unmap operation is the responsibility of the following function. > */ > - unmap_hugepage_align(&op->remap, &unmap_start, &unmap_range); > + unmap_hugepage_align(&op->remap, &unmap_start, &unmap_range, is_sparse); > > /* If the range changed, we might have to lock a wider region to guarantee > * atomicity. panthor_vm_lock_region() bails out early if the new region > @@ -2253,14 +2333,15 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > } > > if (op->remap.prev) { > - struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj); > u64 offset = op->remap.prev->gem.offset + unmap_start - op->remap.prev->va.addr; > u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start; > > + if (!is_sparse) if (op->remap.prev->gem.obj) > + sgt = to_panthor_bo(op->remap.prev->gem.obj)->dmap.sgt; > + > if (!unmap_vma->evicted) { > - ret = panthor_vm_map_pages(vm, unmap_start, > - flags_to_prot(unmap_vma->flags), > - bo->dmap.sgt, offset, size); > + ret = panthor_vm_map(vm, is_sparse, sgt, unmap_start, offset, > + size, flags_to_prot(unmap_vma->flags)); > if (ret) > return ret; > } > @@ -2271,14 +2352,16 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > } > > if (op->remap.next) { > - struct panthor_gem_object *bo = to_panthor_bo(op->remap.next->gem.obj); > u64 addr = op->remap.next->va.addr; > u64 size = unmap_start + unmap_range - op->remap.next->va.addr; > > + if (!is_sparse) if (op->remap.next->gem.obj) > + sgt = to_panthor_bo(op->remap.next->gem.obj)->dmap.sgt; > + > if (!unmap_vma->evicted) { > - ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags), > - bo->dmap.sgt, op->remap.next->gem.offset, > - size); > + ret = panthor_vm_map(vm, is_sparse, sgt, addr, > + op->remap.next->gem.offset, size, > + flags_to_prot(unmap_vma->flags)); > if (ret) > return ret; > } > @@ -2292,20 +2375,22 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > next_vma ? &next_vma->base : NULL, > &op->remap); > > - if (prev_vma) { > - /* panthor_vma_link() transfers the vm_bo ownership to > - * the VMA object. Since the vm_bo we're passing is still > - * owned by the old mapping which will be released when this > - * mapping is destroyed, we need to grab a ref here. > - */ > - panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo); > - } > + if (!is_sparse) { if (op->remap.unmap->va->vm_bo) { > + if (prev_vma) { > + /* panthor_vma_link() transfers the vm_bo ownership to > + * the VMA object. Since the vm_bo we're passing is still > + * owned by the old mapping which will be released when this > + * mapping is destroyed, we need to grab a ref here. > + */ > + panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo); > + } > + > + if (next_vma) > + panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo); > > - if (next_vma) { > - panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo); > + panthor_vma_unlink(unmap_vma); > } > > - panthor_vma_unlink(unmap_vma); > return 0; > } > > @@ -2579,11 +2664,9 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op, > > switch (op_type) { > case DRM_PANTHOR_VM_BIND_OP_TYPE_MAP: { > - const struct drm_gpuvm_map_req map_req = { > + struct drm_gpuvm_map_req map_req = { > .map.va.addr = op->va.addr, > .map.va.range = op->va.range, > - .map.gem.obj = op->map.vm_bo->obj, > - .map.gem.offset = op->map.bo_offset, > }; > > if (vm->unusable) { > @@ -2591,6 +2674,11 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op, > break; > } > > + if (!(op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) { > + map_req.map.gem.obj = op->map.vm_bo->obj; > + map_req.map.gem.offset = op->map.bo_offset; > + } > + > ret = drm_gpuvm_sm_map(&vm->base, vm, &map_req); > break; > } > @@ -3041,6 +3129,9 @@ int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo > struct panthor_vm_op_ctx op_ctx; > int ret; > > + if (drm_WARN_ON(&vm->ptdev->base, flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) > + return -EINVAL; > + > op = (struct drm_panthor_vm_bind_op){ > .bo_offset = offset, > .size = size, > @@ -3204,6 +3295,10 @@ void panthor_mmu_unplug(struct panthor_device *ptdev) > } > } > mutex_unlock(&ptdev->mmu->as.slots_lock); > + > + sg_free_table(ptdev->mmu->dummy.sgt); > + kfree(ptdev->mmu->dummy.sgt); > + __free_pages(ptdev->mmu->dummy.pages, get_order(SZ_2M)); We can probably replace this by drmm_actions so the dummy pages and sgt gets automatically freed when the DRM device vanishes and you don't have to duplicate the logic in the unplug and panthor_mmu_init error path. > } > > static void panthor_mmu_release_wq(struct drm_device *ddev, void *res) > @@ -3269,7 +3364,33 @@ int panthor_mmu_init(struct panthor_device *ptdev) > ptdev->gpu_info.mmu_features |= BITS_PER_LONG; > } > > + mmu->dummy.pages = alloc_pages(GFP_KERNEL | __GFP_ZERO, get_order(SZ_2M)); > + if (!mmu->dummy.pages) > + return -ENOMEM; We should probably allocate this page on-demand, so that systems that never use sparse bindings don't pay the 2M cost. Also, we might want a fallback to use 4k pages in case the 2M page allocation fails. > + > + mmu->dummy.sgt = kzalloc_obj(*mmu->dummy.sgt); > + if (!mmu->dummy.sgt) { > + ret = -ENOMEM; > + goto mmu_init_free_pages; > + } > + > + ret = sg_alloc_table(mmu->dummy.sgt, 1, GFP_KERNEL); > + if (ret) { > + ret = -ENOMEM; > + goto mmu_init_free_sgtable; > + } > + > + sg_set_page(mmu->dummy.sgt->sgl, mmu->dummy.pages, SZ_2M, 0); > + sg_dma_address(mmu->dummy.sgt->sgl) = page_to_phys(mmu->dummy.pages); > + sg_dma_len(mmu->dummy.sgt->sgl) = SZ_2M; Let's use drm_prime_pages_to_sg() instead of open-coding it: mmu->dummy.sgt = drm_prime_pages_to_sg(&ptdev->base, &mmu->dummy.pages, 1); if (IS_ERR(mmu->dummy.sgt)) ... > + > return drmm_add_action_or_reset(&ptdev->base, panthor_mmu_release_wq, mmu->vm.wq); > + > +mmu_init_free_sgtable: > + kfree(mmu->dummy.sgt); > +mmu_init_free_pages: > + __free_pages(mmu->dummy.pages, get_order(SZ_2M)); > + return ret; > } > > #ifdef CONFIG_DEBUG_FS > diff --git a/include/uapi/drm/panthor_drm.h b/include/uapi/drm/panthor_drm.h > index dc2704fc2829..9e7197845ea4 100644 > --- a/include/uapi/drm/panthor_drm.h > +++ b/include/uapi/drm/panthor_drm.h > @@ -614,6 +614,18 @@ enum drm_panthor_vm_bind_op_flags { > */ > DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED = 1 << 2, > > + /** > + * @DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE: Repeat a BO range > + * > + * Only valid with DRM_PANTHOR_VM_BIND_OP_TYPE_MAP. > + * > + * When this flag is set, the whole vm_bind range is mapped over a dummy > + * object in a cyclic fashion, and all GPU reads from the addresses in the > + * range return 0. Nope, reads return undefined values and writes hit the dummy page (hence the undefined value on reads). This makes me realize we probably don't want to make the dummy page global (shared across processes), because that's a potential source of data leak. > This flag being set means drm_panthor_vm_bind_op:offset > + * and drm_panthor_vm_bind_op::handle must both be set to 0. > + */ > + DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE = 1 << 3, > + > /** > * @DRM_PANTHOR_VM_BIND_OP_TYPE_MASK: Mask used to determine the type of operation. > */ > @@ -677,7 +689,6 @@ struct drm_panthor_vm_bind_op { > * This array shall not be empty for sync-only operations. > */ > struct drm_panthor_obj_array syncs; > - Looks like an unrelated change that should be in its own commit. > }; > > /** ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v6 4/4] drm/panthor: Bump the driver version to 1.9 2026-04-03 19:27 [PATCH v6 0/4] Support sparse mappings in Panthor Adrián Larumbe ` (2 preceding siblings ...) 2026-04-03 19:27 ` [PATCH v6 3/4] drm/panthor: Support sparse mappings Adrián Larumbe @ 2026-04-03 19:27 ` Adrián Larumbe 3 siblings, 0 replies; 7+ messages in thread From: Adrián Larumbe @ 2026-04-03 19:27 UTC (permalink / raw) To: linux-kernel Cc: dri-devel, Steven Price, Boris Brezillon, kernel, Adrián Larumbe, Liviu Dudau, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter Bump the driver version to reflect the new MMU info query ioctl parameter and the VM_BIND map sparse flag. Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> --- drivers/gpu/drm/panthor/panthor_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index a8090bc4e33c..c8fb63ede62c 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -1787,6 +1787,8 @@ static void panthor_debugfs_init(struct drm_minor *minor) * - adds DRM_IOCTL_PANTHOR_BO_QUERY_INFO ioctl * - adds drm_panthor_gpu_info::selected_coherency * - 1.8 - extends DEV_QUERY_TIMESTAMP_INFO with flags + * - 1.9 - adds DRM_PANTHOR_DEV_QUERY_MMU_INFO query + * - adds DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE flag */ static const struct drm_driver panthor_drm_driver = { .driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ | -- 2.53.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-07 12:35 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-03 19:27 [PATCH v6 0/4] Support sparse mappings in Panthor Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 1/4] drm/panthor: Expose GPU page sizes to UM Adrián Larumbe 2026-04-03 19:27 ` [PATCH v6 2/4] drm/panthor: Pass vm_bind_op to vm_prepare_map_op_ctx Adrián Larumbe 2026-04-07 10:44 ` Liviu Dudau 2026-04-03 19:27 ` [PATCH v6 3/4] drm/panthor: Support sparse mappings Adrián Larumbe 2026-04-07 12:34 ` Boris Brezillon 2026-04-03 19:27 ` [PATCH v6 4/4] drm/panthor: Bump the driver version to 1.9 Adrián Larumbe
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox