* [PATCH v2 0/1] Workaround for partial huge page unmaps in Panthor @ 2025-11-27 3:50 Adrián Larumbe 2025-11-27 3:50 ` [PATCH v2 1/1] drm/panthor: Support partial unmaps of huge pages Adrián Larumbe 0 siblings, 1 reply; 3+ messages in thread From: Adrián Larumbe @ 2025-11-27 3:50 UTC (permalink / raw) To: linux-kernel Cc: dri-devel, Steven Price, Boris Brezillon, kernel, Adrián Larumbe This is v2 of [1]. This patch is a workaround for performing partial unmaps of a VM region backed by huge pages. Since these are now disallowed, the patch makes sure unmaps are done on a backing page-granularity, and then regions untouched by the VM_BIND unmap operation are restored. A patch series with IGT tests to validate this functionality is found at [2]. Changelog: v2: - Fixed bug caused by confusion between semantics of gpu_va prev and next ops boundaries and those of the original vma object. - Coalesce all unmap operations into a single one. - Refactored and simplified code. [1] https://lore.kernel.org/dri-devel/20251019032108.3498086-1-adrian.larumbe@collabora.com/ [2] https://lore.kernel.org/igt-dev/20251127030145.585641-1-adrian.larumbe@collabora.com/T/#t Adrián Larumbe (1): drm/panthor: Support partial unmaps of huge pages drivers/gpu/drm/panthor/panthor_mmu.c | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) -- 2.51.2 ^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 1/1] drm/panthor: Support partial unmaps of huge pages 2025-11-27 3:50 [PATCH v2 0/1] Workaround for partial huge page unmaps in Panthor Adrián Larumbe @ 2025-11-27 3:50 ` Adrián Larumbe 2025-11-27 9:11 ` Boris Brezillon 0 siblings, 1 reply; 3+ messages in thread From: Adrián Larumbe @ 2025-11-27 3:50 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 Commit 33729a5fc0ca ("iommu/io-pgtable-arm: Remove split on unmap behavior") did away with the treatment of partial unmaps of huge IOPTEs. In the case of Panthor, that means an attempt to run a VM_BIND unmap operation on a memory region whose start address and size aren't 2MiB aligned, in the event it intersects with a huge page, would lead to ARM IOMMU management code to fail and a warning being raised. Presently, and for lack of a better alternative, it's best to have Panthor handle partial unmaps at the driver level, by unmapping entire huge pages and remapping the difference between them and the requested unmap region. This could change in the future when the VM_BIND uAPI is expanded to enforce huge page alignment and map/unmap operational constraints that render this code unnecessary. Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> --- drivers/gpu/drm/panthor/panthor_mmu.c | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 183da30fa500..41d7974c95ea 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -2110,6 +2110,57 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) return 0; } +static bool +is_huge_page(const struct panthor_vma *unmap_vma, u64 addr) +{ + const struct page *pg; + pgoff_t bo_offset; + + bo_offset = addr - unmap_vma->base.va.addr + unmap_vma->base.gem.offset; + pg = to_panthor_bo(unmap_vma->base.gem.obj)->base.pages[bo_offset >> PAGE_SHIFT]; + + return (folio_order(page_folio(pg)) >= PMD_ORDER); +} + +struct remap_params { + u64 prev_remap_start, prev_remap_range; + u64 next_remap_start, next_remap_range; +}; + +static struct remap_params +get_map_unmap_intervals(const struct drm_gpuva_op_remap *op, + const struct panthor_vma *unmap_vma, + u64 *unmap_start, u64 *unmap_range) +{ + u64 aligned_unmap_start, aligned_unmap_end, unmap_end; + struct remap_params params = {0}; + + drm_gpuva_op_remap_to_unmap_range(op, unmap_start, unmap_range); + unmap_end = *unmap_start + *unmap_range; + + aligned_unmap_start = ALIGN_DOWN(*unmap_start, SZ_2M); + + if (aligned_unmap_start < *unmap_start && + unmap_vma->base.va.addr <= aligned_unmap_start && + is_huge_page(unmap_vma, *unmap_start)) { + params.prev_remap_start = aligned_unmap_start; + params.prev_remap_range = *unmap_start & (SZ_2M - 1); + *unmap_range += *unmap_start - aligned_unmap_start; + *unmap_start = aligned_unmap_start; + } + + aligned_unmap_end = ALIGN(unmap_end, SZ_2M); + + if (aligned_unmap_end > unmap_end && + (unmap_vma->base.va.addr + unmap_vma->base.va.range >= aligned_unmap_end) && + is_huge_page(unmap_vma, unmap_end - 1)) { + *unmap_range += params.next_remap_range = aligned_unmap_end - unmap_end; + params.next_remap_start = unmap_end; + } + + return params; +} + static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, void *priv) { @@ -2118,19 +2169,44 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; struct panthor_vma *prev_vma = NULL, *next_vma = NULL; u64 unmap_start, unmap_range; + struct remap_params params; int ret; drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, &unmap_range); + + /* + * ARM IOMMU page table management code disallows partial unmaps of huge pages, + * so when a partial unmap is requested, we must first unmap the entire huge + * page and then remap the difference between the huge page minus the requested + * unmap region. Calculating the right offsets and ranges for the different unmap + * and map operations is the responsibility of the following function. + */ + params = get_map_unmap_intervals(&op->remap, unmap_vma, &unmap_start, &unmap_range); + ret = panthor_vm_unmap_pages(vm, unmap_start, unmap_range); if (ret) return ret; if (op->remap.prev) { + ret = panthor_vm_map_pages(vm, params.prev_remap_start, + flags_to_prot(unmap_vma->flags), + to_drm_gem_shmem_obj(op->remap.prev->gem.obj)->sgt, + op->remap.prev->gem.offset, params.prev_remap_range); + if (ret) + return ret; + prev_vma = panthor_vm_op_ctx_get_vma(op_ctx); panthor_vma_init(prev_vma, unmap_vma->flags); } if (op->remap.next) { + ret = panthor_vm_map_pages(vm, params.next_remap_start, + flags_to_prot(unmap_vma->flags), + to_drm_gem_shmem_obj(op->remap.next->gem.obj)->sgt, + op->remap.next->gem.offset, params.next_remap_range); + if (ret) + return ret; + next_vma = panthor_vm_op_ctx_get_vma(op_ctx); panthor_vma_init(next_vma, unmap_vma->flags); } -- 2.51.2 ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v2 1/1] drm/panthor: Support partial unmaps of huge pages 2025-11-27 3:50 ` [PATCH v2 1/1] drm/panthor: Support partial unmaps of huge pages Adrián Larumbe @ 2025-11-27 9:11 ` Boris Brezillon 0 siblings, 0 replies; 3+ messages in thread From: Boris Brezillon @ 2025-11-27 9:11 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 On Thu, 27 Nov 2025 03:50:13 +0000 Adrián Larumbe <adrian.larumbe@collabora.com> wrote: > Commit 33729a5fc0ca ("iommu/io-pgtable-arm: Remove split on unmap > behavior") did away with the treatment of partial unmaps of huge IOPTEs. > > In the case of Panthor, that means an attempt to run a VM_BIND unmap > operation on a memory region whose start address and size aren't 2MiB > aligned, in the event it intersects with a huge page, would lead to ARM > IOMMU management code to fail and a warning being raised. > > Presently, and for lack of a better alternative, it's best to have > Panthor handle partial unmaps at the driver level, by unmapping entire > huge pages and remapping the difference between them and the requested > unmap region. > > This could change in the future when the VM_BIND uAPI is expanded to > enforce huge page alignment and map/unmap operational constraints that > render this code unnecessary. > > Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com> > --- > drivers/gpu/drm/panthor/panthor_mmu.c | 76 +++++++++++++++++++++++++++ > 1 file changed, 76 insertions(+) > > diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c > index 183da30fa500..41d7974c95ea 100644 > --- a/drivers/gpu/drm/panthor/panthor_mmu.c > +++ b/drivers/gpu/drm/panthor/panthor_mmu.c > @@ -2110,6 +2110,57 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) > return 0; > } > > +static bool > +is_huge_page(const struct panthor_vma *unmap_vma, u64 addr) The function name doesn't really match the arguments it's being passed. I'd rename this function iova_mapped_as_huge_page(). I'd also rename unmap_vma into vma (the helper doesn't have to know that the test is used for unmapping), and addr into iova. > +{ > + const struct page *pg; > + pgoff_t bo_offset; > + > + bo_offset = addr - unmap_vma->base.va.addr + unmap_vma->base.gem.offset; > + pg = to_panthor_bo(unmap_vma->base.gem.obj)->base.pages[bo_offset >> PAGE_SHIFT]; > + > + return (folio_order(page_folio(pg)) >= PMD_ORDER); I don't think we should use PMD_ORDER for this test, because the GPU MMU page size might differ from the CPU one, and what we care about here is whether this page is huge from the GPU MMU perspective. IOW, we should have: return folio_size(page_folio(pg)) >= SZ_2M; > +} > + > +struct remap_params { > + u64 prev_remap_start, prev_remap_range; > + u64 next_remap_start, next_remap_range; > +}; > + > +static struct remap_params > +get_map_unmap_intervals(const struct drm_gpuva_op_remap *op, > + const struct panthor_vma *unmap_vma, > + u64 *unmap_start, u64 *unmap_range) > +{ > + u64 aligned_unmap_start, aligned_unmap_end, unmap_end; > + struct remap_params params = {0}; > + > + drm_gpuva_op_remap_to_unmap_range(op, unmap_start, unmap_range); > + unmap_end = *unmap_start + *unmap_range; > + > + aligned_unmap_start = ALIGN_DOWN(*unmap_start, SZ_2M); > + > + if (aligned_unmap_start < *unmap_start && > + unmap_vma->base.va.addr <= aligned_unmap_start && > + is_huge_page(unmap_vma, *unmap_start)) { > + params.prev_remap_start = aligned_unmap_start; > + params.prev_remap_range = *unmap_start & (SZ_2M - 1); > + *unmap_range += *unmap_start - aligned_unmap_start; > + *unmap_start = aligned_unmap_start; > + } > + > + aligned_unmap_end = ALIGN(unmap_end, SZ_2M); > + > + if (aligned_unmap_end > unmap_end && > + (unmap_vma->base.va.addr + unmap_vma->base.va.range >= aligned_unmap_end) && > + is_huge_page(unmap_vma, unmap_end - 1)) { > + *unmap_range += params.next_remap_range = aligned_unmap_end - unmap_end; Let's do that in two steps to make it more readable please: params.next_remap_range = aligned_unmap_end - unmap_end; *unmap_range += params.next_remap_range; > + params.next_remap_start = unmap_end; > + } > + > + return params; > +} > + > static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > void *priv) > { > @@ -2118,19 +2169,44 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, > struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; > struct panthor_vma *prev_vma = NULL, *next_vma = NULL; > u64 unmap_start, unmap_range; > + struct remap_params params; > int ret; > > drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, &unmap_range); > + > + /* > + * ARM IOMMU page table management code disallows partial unmaps of huge pages, > + * so when a partial unmap is requested, we must first unmap the entire huge > + * page and then remap the difference between the huge page minus the requested > + * unmap region. Calculating the right offsets and ranges for the different unmap > + * and map operations is the responsibility of the following function. > + */ > + params = get_map_unmap_intervals(&op->remap, unmap_vma, &unmap_start, &unmap_range); > + > ret = panthor_vm_unmap_pages(vm, unmap_start, unmap_range); > if (ret) > return ret; > > if (op->remap.prev) { > + ret = panthor_vm_map_pages(vm, params.prev_remap_start, > + flags_to_prot(unmap_vma->flags), > + to_drm_gem_shmem_obj(op->remap.prev->gem.obj)->sgt, > + op->remap.prev->gem.offset, params.prev_remap_range); > + if (ret) > + return ret; > + > prev_vma = panthor_vm_op_ctx_get_vma(op_ctx); > panthor_vma_init(prev_vma, unmap_vma->flags); > } > > if (op->remap.next) { > + ret = panthor_vm_map_pages(vm, params.next_remap_start, > + flags_to_prot(unmap_vma->flags), > + to_drm_gem_shmem_obj(op->remap.next->gem.obj)->sgt, > + op->remap.next->gem.offset, params.next_remap_range); > + if (ret) > + return ret; > + > next_vma = panthor_vm_op_ctx_get_vma(op_ctx); > panthor_vma_init(next_vma, unmap_vma->flags); > } ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2025-11-27 9:11 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-11-27 3:50 [PATCH v2 0/1] Workaround for partial huge page unmaps in Panthor Adrián Larumbe 2025-11-27 3:50 ` [PATCH v2 1/1] drm/panthor: Support partial unmaps of huge pages Adrián Larumbe 2025-11-27 9:11 ` Boris Brezillon
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox