]> git.baikalelectronics.ru Git - kernel.git/commitdiff
drm/panfrost: Calculate lock region size correctly
authorSteven Price <steven.price@arm.com>
Fri, 3 Sep 2021 09:49:57 +0000 (10:49 +0100)
committerSteven Price <steven.price@arm.com>
Fri, 17 Sep 2021 10:47:26 +0000 (11:47 +0100)
It turns out that when locking a region, the region must be a naturally
aligned power of 2. The upshot of this is that if the desired region
crosses a 'large boundary' the region size must be increased
significantly to ensure that the locked region completely covers the
desired region. Previous calculations (including in kbase for the
proprietary driver) failed to take this into account.

Since it's known that the lock region must be naturally aligned we can
compute the required size by looking at the highest bit position which
changes between the start/end of the lock region (subtracting 1 from the
end because the end address is exclusive). The start address is then
aligned based on the size (this is technically unnecessary as the
hardware will ignore these bits, but the spec advises to do this "to
avoid confusion").

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com>
Signed-off-by: Steven Price <steven.price@arm.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20210903094957.74560-1-steven.price@arm.com
drivers/gpu/drm/panfrost/panfrost_mmu.c

index f7f83cf63f42b0af1baba4958b2f0abfa47015d6..f51d3f791a17a931a5bd229c99ff7445a13813e6 100644 (file)
@@ -58,17 +58,33 @@ static int write_cmd(struct panfrost_device *pfdev, u32 as_nr, u32 cmd)
 }
 
 static void lock_region(struct panfrost_device *pfdev, u32 as_nr,
-                       u64 iova, u64 size)
+                       u64 region_start, u64 size)
 {
        u8 region_width;
-       u64 region = iova & PAGE_MASK;
+       u64 region;
+       u64 region_end = region_start + size;
 
-       /* The size is encoded as ceil(log2) minus(1), which may be calculated
-        * with fls. The size must be clamped to hardware bounds.
+       if (!size)
+               return;
+
+       /*
+        * The locked region is a naturally aligned power of 2 block encoded as
+        * log2 minus(1).
+        * Calculate the desired start/end and look for the highest bit which
+        * differs. The smallest naturally aligned block must include this bit
+        * change, the desired region starts with this bit (and subsequent bits)
+        * zeroed and ends with the bit (and subsequent bits) set to one.
         */
-       size = max_t(u64, size, AS_LOCK_REGION_MIN_SIZE);
-       region_width = fls64(size - 1) - 1;
-       region |= region_width;
+       region_width = max(fls64(region_start ^ (region_end - 1)),
+                          const_ilog2(AS_LOCK_REGION_MIN_SIZE)) - 1;
+
+       /*
+        * Mask off the low bits of region_start (which would be ignored by
+        * the hardware anyway)
+        */
+       region_start &= GENMASK_ULL(63, region_width);
+
+       region = region_width | region_start;
 
        /* Lock the region that needs to be updated */
        mmu_write(pfdev, AS_LOCKADDR_LO(as_nr), lower_32_bits(region));