A migrate.src[] entry with MIGRATE_PFN_VALID=0 && MIGRATE_PFN_COMPOUND=1
represents a compound (THP) source page, but migrate_pfn_to_page() returns
NULL for it. Both accounting loops in drm_pagemap_migrate_to_devmem()
derive the count from that NULL page and fall back to nr_pages = 1 instead
of HPAGE_PMD_NR, undercounting migrated_pages and risking a spurious
-EBUSY in the final migration race check. drm_pagemap_cpages() already
handles this case, so the open-coded sites disagree.

Factor the decoding out into drm_pagemap_src_pfn_nr_pages() and use it in
drm_pagemap_cpages() and both accounting loops of
drm_pagemap_migrate_to_devmem(), so a COMPOUND-only entry is consistently
counted as HPAGE_PMD_NR base pages.

Fixes: 192cb1f5cb16 ("drm/pagemap: Enable THP support for GPU memory migration")
Signed-off-by: Junhua Shen <[email protected]>
Reviewed-by: Francois Dugast <[email protected]>
---
v2:
- Reframe as a bug fix: a MIGRATE_PFN_VALID=0 && MIGRATE_PFN_COMPOUND=1
  entry was undercounted as 1 base page instead of HPAGE_PMD_NR, risking a
  spurious -EBUSY. Rewrite the commit message accordingly and add a Fixes
  tag.
- Add Reviewed-by: Francois Dugast.
- No functional change to the diff since v1.

 drivers/gpu/drm/drm_pagemap.c | 60 +++++++++++++++++++++++++----------
 1 file changed, 43 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c
index 892b325fa99b..a31cc4f0af68 100644
--- a/drivers/gpu/drm/drm_pagemap.c
+++ b/drivers/gpu/drm/drm_pagemap.c
@@ -554,6 +554,37 @@ static int drm_pagemap_migrate_range(struct 
drm_pagemap_devmem *devmem,
        return ret;
 }
 
+/**
+ * drm_pagemap_src_pfn_nr_pages() - Decode src entry and return base-page count
+ * @src_pfn: Source migrate entry
+ * @src_page: Optional decoded source page when MIGRATE_PFN_VALID is set
+ *
+ * Decode @src_pfn to compute how many base pages it represents: use folio
+ * page count for valid entries, HPAGE_PMD_NR for COMPOUND-only entries,
+ * otherwise 1.
+ *
+ * Return: Number of base pages represented by @src_pfn.
+ */
+static unsigned long drm_pagemap_src_pfn_nr_pages(unsigned long src_pfn,
+                                                  struct page **src_page)
+{
+       struct page *page = NULL;
+       unsigned long nr_pages = 1;
+
+       if (src_pfn & MIGRATE_PFN_VALID) {
+               page = migrate_pfn_to_page(src_pfn);
+               if (page)
+                       nr_pages = NR_PAGES(folio_order(page_folio(page)));
+       } else if (src_pfn & MIGRATE_PFN_COMPOUND) {
+               nr_pages = HPAGE_PMD_NR;
+       }
+
+       if (src_page)
+               *src_page = page;
+
+       return nr_pages;
+}
+
 /**
  * drm_pagemap_cpages() - Count collected pages
  * @migrate_pfn: Array of migrate_pfn entries to account
@@ -570,20 +601,14 @@ static int drm_pagemap_cpages(unsigned long *migrate_pfn, 
unsigned long npages)
        unsigned long i, cpages = 0;
 
        for (i = 0; i < npages;) {
-               struct page *page = migrate_pfn_to_page(migrate_pfn[i]);
-               struct folio *folio;
-               unsigned int order = 0;
+               unsigned long src_pfn = migrate_pfn[i];
+               struct page *page;
+               unsigned long nr_pages = drm_pagemap_src_pfn_nr_pages(src_pfn, 
&page);
 
-               if (page) {
-                       folio = page_folio(page);
-                       order = folio_order(folio);
-                       cpages += NR_PAGES(order);
-               } else if (migrate_pfn[i] & MIGRATE_PFN_COMPOUND) {
-                       order = HPAGE_PMD_ORDER;
-                       cpages += NR_PAGES(order);
-               }
+               if (page || (src_pfn & MIGRATE_PFN_COMPOUND))
+                       cpages += nr_pages;
 
-               i += NR_PAGES(order);
+               i += nr_pages;
        }
 
        return cpages;
@@ -703,8 +728,9 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem 
*devmem_allocation,
 
        /* Count device-private pages to migrate */
        for (i = 0; i < npages;) {
-               struct page *src_page = migrate_pfn_to_page(migrate.src[i]);
-               unsigned long nr_pages = src_page ? 
NR_PAGES(folio_order(page_folio(src_page))) : 1;
+               unsigned long src_pfn = migrate.src[i];
+               struct page *src_page;
+               unsigned long nr_pages = drm_pagemap_src_pfn_nr_pages(src_pfn, 
&src_page);
 
                if (src_page && is_zone_device_page(src_page)) {
                        if (page_pgmap(src_page) == pagemap)
@@ -818,10 +844,10 @@ int drm_pagemap_migrate_to_devmem(struct 
drm_pagemap_devmem *devmem_allocation,
        migrate_vma_pages(&migrate);
 
        for (i = 0; !err && i < npages;) {
-               struct page *page = migrate_pfn_to_page(migrate.src[i]);
-               unsigned long nr_pages = page ? 
NR_PAGES(folio_order(page_folio(page))) : 1;
+               unsigned long src_pfn = migrate.src[i];
+               unsigned long nr_pages = drm_pagemap_src_pfn_nr_pages(src_pfn, 
NULL);
 
-               if (migrate.src[i] & MIGRATE_PFN_MIGRATE)
+               if (src_pfn & MIGRATE_PFN_MIGRATE)
                        migrated_pages += nr_pages;
 
                i += nr_pages;
-- 
2.34.1

Reply via email to