nouveau_dmem_folio_free() drops chunk->callocated once per freed folio,
while a large (compound) device-private folio is only counted once when
it is allocated.  When such a folio is split, the mm core invokes
->folio_split() (nouveau_dmem_folio_split()) once for each new
sub-folio, but the hook only fixes up the sub-folio metadata and leaves
chunk->callocated unchanged.

Each resulting sub-folio is later freed separately, so after a split
the single allocation (+1) is met by N frees (-N), leaving
chunk->callocated short by N-1.  On the first split/free cycle it
underflows: WARN_ON(!chunk->callocated) fires, the unsigned counter
wraps and never returns to zero, so the chunk can no longer be
reclaimed (nouveau_dmem_fini() also warns on the leaked count).

Account for the new sub-folio in the split hook, under the same lock as
nouveau_dmem_folio_free(), so the count stays balanced.

Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory 
migration")
Reported-by: Yuhao Jiang <[email protected]>
Assisted-by: Claude:claude-opus-5
Cc: [email protected]
Signed-off-by: Zhenhao Wan <[email protected]>
---
 drivers/gpu/drm/nouveau/nouveau_dmem.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c 
b/drivers/gpu/drm/nouveau/nouveau_dmem.c
index d2abee3efb9a..ad4570c50be7 100644
--- a/drivers/gpu/drm/nouveau/nouveau_dmem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c
@@ -279,11 +279,25 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct 
vm_fault *vmf)
 
 static void nouveau_dmem_folio_split(struct folio *head, struct folio *tail)
 {
+       struct nouveau_dmem_chunk *chunk;
+       struct nouveau_dmem *dmem;
+
        if (tail == NULL)
                return;
        tail->pgmap = head->pgmap;
        tail->mapping = head->mapping;
        folio_set_zone_device_data(tail, folio_zone_device_data(head));
+
+       /*
+        * The split hands out a new independently-freeable folio that will
+        * later be released via nouveau_dmem_folio_free(); account for it so
+        * chunk->callocated stays balanced.
+        */
+       chunk = nouveau_page_to_chunk(&head->page);
+       dmem = chunk->drm->dmem;
+       spin_lock(&dmem->lock);
+       chunk->callocated++;
+       spin_unlock(&dmem->lock);
 }
 
 static const struct dev_pagemap_ops nouveau_dmem_pagemap_ops = {

-- 
2.34.1

Reply via email to