]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/msm: Add PRR support
authorRob Clark <robdclark@chromium.org>
Sun, 29 Jun 2025 20:13:01 +0000 (13:13 -0700)
committerRob Clark <robin.clark@oss.qualcomm.com>
Sat, 5 Jul 2025 00:48:35 +0000 (17:48 -0700)
Add PRR (Partial Resident Region) is a bypass address which make GPU
writes go to /dev/null and reads return zero.  This is used to implement
vulkan sparse residency.

To support PRR/NULL mappings, we allocate a page to reserve a physical
address which we know will not be used as part of a GEM object, and
configure the SMMU to use this address for PRR/NULL mappings.

Signed-off-by: Rob Clark <robdclark@chromium.org>
Signed-off-by: Rob Clark <robin.clark@oss.qualcomm.com>
Tested-by: Antonino Maniscalco <antomani103@gmail.com>
Reviewed-by: Antonino Maniscalco <antomani103@gmail.com>
Patchwork: https://patchwork.freedesktop.org/patch/661486/

drivers/gpu/drm/msm/adreno/adreno_gpu.c
drivers/gpu/drm/msm/msm_iommu.c
include/uapi/drm/msm_drm.h

index 676fc078d545a7e77eb4883f7429fbde6255c5c6..12bf39c0516cbbd6dec5d1208531a518148d6921 100644 (file)
@@ -357,6 +357,13 @@ int adreno_fault_handler(struct msm_gpu *gpu, unsigned long iova, int flags,
        return 0;
 }
 
+static bool
+adreno_smmu_has_prr(struct msm_gpu *gpu)
+{
+       struct adreno_smmu_priv *adreno_smmu = dev_get_drvdata(&gpu->pdev->dev);
+       return adreno_smmu && adreno_smmu->set_prr_addr;
+}
+
 int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx,
                     uint32_t param, uint64_t *value, uint32_t *len)
 {
@@ -440,6 +447,9 @@ int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx,
        case MSM_PARAM_UCHE_TRAP_BASE:
                *value = adreno_gpu->uche_trap_base;
                return 0;
+       case MSM_PARAM_HAS_PRR:
+               *value = adreno_smmu_has_prr(gpu);
+               return 0;
        default:
                return UERR(EINVAL, drm, "%s: invalid param: %u", gpu->name, param);
        }
index 3c2eb59bfd499870132d971f94059106d43b0f15..a0c74ecdb11b8eb091a5d4c4f88e109ea6c92eed 100644 (file)
@@ -13,6 +13,7 @@ struct msm_iommu {
        struct msm_mmu base;
        struct iommu_domain *domain;
        atomic_t pagetables;
+       struct page *prr_page;
 };
 
 #define to_msm_iommu(x) container_of(x, struct msm_iommu, base)
@@ -112,6 +113,36 @@ static int msm_iommu_pagetable_unmap(struct msm_mmu *mmu, u64 iova,
        return (size == 0) ? 0 : -EINVAL;
 }
 
+static int msm_iommu_pagetable_map_prr(struct msm_mmu *mmu, u64 iova, size_t len, int prot)
+{
+       struct msm_iommu_pagetable *pagetable = to_pagetable(mmu);
+       struct io_pgtable_ops *ops = pagetable->pgtbl_ops;
+       struct msm_iommu *iommu = to_msm_iommu(pagetable->parent);
+       phys_addr_t phys = page_to_phys(iommu->prr_page);
+       u64 addr = iova;
+
+       while (len) {
+               size_t mapped = 0;
+               size_t size = PAGE_SIZE;
+               int ret;
+
+               ret = ops->map_pages(ops, addr, phys, size, 1, prot, GFP_KERNEL, &mapped);
+
+               /* map_pages could fail after mapping some of the pages,
+                * so update the counters before error handling.
+                */
+               addr += mapped;
+               len  -= mapped;
+
+               if (ret) {
+                       msm_iommu_pagetable_unmap(mmu, iova, addr - iova);
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static int msm_iommu_pagetable_map(struct msm_mmu *mmu, u64 iova,
                                   struct sg_table *sgt, size_t off, size_t len,
                                   int prot)
@@ -122,6 +153,9 @@ static int msm_iommu_pagetable_map(struct msm_mmu *mmu, u64 iova,
        u64 addr = iova;
        unsigned int i;
 
+       if (!sgt)
+               return msm_iommu_pagetable_map_prr(mmu, iova, len, prot);
+
        for_each_sgtable_sg(sgt, sg, i) {
                size_t size = sg->length;
                phys_addr_t phys = sg_phys(sg);
@@ -177,9 +211,16 @@ static void msm_iommu_pagetable_destroy(struct msm_mmu *mmu)
         * If this is the last attached pagetable for the parent,
         * disable TTBR0 in the arm-smmu driver
         */
-       if (atomic_dec_return(&iommu->pagetables) == 0)
+       if (atomic_dec_return(&iommu->pagetables) == 0) {
                adreno_smmu->set_ttbr0_cfg(adreno_smmu->cookie, NULL);
 
+               if (adreno_smmu->set_prr_bit) {
+                       adreno_smmu->set_prr_bit(adreno_smmu->cookie, false);
+                       __free_page(iommu->prr_page);
+                       iommu->prr_page = NULL;
+               }
+       }
+
        free_io_pgtable_ops(pagetable->pgtbl_ops);
        kfree(pagetable);
 }
@@ -336,6 +377,25 @@ struct msm_mmu *msm_iommu_pagetable_create(struct msm_mmu *parent)
                        kfree(pagetable);
                        return ERR_PTR(ret);
                }
+
+               BUG_ON(iommu->prr_page);
+               if (adreno_smmu->set_prr_bit) {
+                       /*
+                        * We need a zero'd page for two reasons:
+                        *
+                        * 1) Reserve a known physical address to use when
+                        *    mapping NULL / sparsely resident regions
+                        * 2) Read back zero
+                        *
+                        * It appears the hw drops writes to the PRR region
+                        * on the floor, but reads actually return whatever
+                        * is in the PRR page.
+                        */
+                       iommu->prr_page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+                       adreno_smmu->set_prr_addr(adreno_smmu->cookie,
+                                                 page_to_phys(iommu->prr_page));
+                       adreno_smmu->set_prr_bit(adreno_smmu->cookie, true);
+               }
        }
 
        /* Needed later for TLB flush */
index 2342cb90857e0ecfe1c1f43f795be21080535ddb..5bc5e4526ccf99c68b6dd98ed017603449e93223 100644 (file)
@@ -91,6 +91,8 @@ struct drm_msm_timespec {
 #define MSM_PARAM_UBWC_SWIZZLE 0x12 /* RO */
 #define MSM_PARAM_MACROTILE_MODE 0x13 /* RO */
 #define MSM_PARAM_UCHE_TRAP_BASE 0x14 /* RO */
+/* PRR (Partially Resident Region) is required for sparse residency: */
+#define MSM_PARAM_HAS_PRR    0x15  /* RO */
 
 /* For backwards compat.  The original support for preemption was based on
  * a single ring per priority level so # of priority levels equals the #