Skip to contentSkip to conversation

mm/cma: don't release CMA pages still in use

RFC PATCH Superseded Rik van Riel 1 participant 2 messages Aug 10

Subsystems (2)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 mm/cma: don't release CMA pages still in use

When a driver calls dma_free_contiguous() before quiescing DMA, the page still has a reference from the device. put_page_testzero() there returns false, ret is incremented, WARN fires, but the code proceeds to free_contig_frozen_range() putting a live page onto buddy and clearing the bitmap. Later cma_alloc() hands the same PFN to a new owner while the original holder still references it.

A concurrent put_page() that drops the last reference between the testzero loop and free_contig_frozen_range() can double-queue the page via page->lru, corrupting buddy lists.

Fix by freeing already-frozen pages in contiguous runs via __cma_release_frozen(), while skipping still-referenced pages. The CMA address space for pages that are still in use at cma_release() time gets leaked, but the pages themselves will get freed once the user drops the last refcount.

This change should be safe because nothing can get reallocated while it is still in use.

Fixes 9bda131c6093 ("mm: cma: add cma_alloc_frozen{_compound}()")
Assisted-by Hermes:muse-spark-1.2
---
 mm/cma.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
mm/cma.c +29 -7
index a13ce4999b39..66952bb03abb 100644
@@ -1018,20 +1018,42 @@ bool cma_release(struct cma *cma, const struct page *pages,
 		 unsigned long count)
 {
 	struct cma_memrange *cmr;
-	unsigned long ret = 0;
+	unsigned long skipped = 0;
 	unsigned long i, pfn;
+	unsigned long base_pfn;
+	unsigned long run_start = 0;
+	unsigned long run_len = 0;
 
 	cmr = find_cma_memrange(cma, pages, count);
 	if (!cmr)
 		return false;
 
-	pfn = page_to_pfn(pages);
-	for (i = 0; i < count; i++, pfn++)
-		ret += !put_page_testzero(pfn_to_page(pfn));
-
-	WARN(ret, "%lu pages are still in use!\n", ret);
+	base_pfn = page_to_pfn(pages);
+	pfn = base_pfn;
+	for (i = 0; i < count; i++, pfn++) {
+		if (put_page_testzero(pfn_to_page(pfn))) {
+			/* Add it to the batch. */
+			if (run_len == 0)
+				run_start = pfn;
+			run_len++;
+		} else {
+			/*
+			 * This page is still in use! Free the freeable
+			 * pages encountered so far, but skip this page.
+			 */
+			if (run_len) {
+				__cma_release_frozen(cma, cmr,
+						     pfn_to_page(run_start),
+						     run_len);
+				run_len = 0;
+			}
+			skipped++;
+		}
+	}
+	if (run_len)
+		__cma_release_frozen(cma, cmr, pfn_to_page(run_start), run_len);
 
-	__cma_release_frozen(cma, cmr, pages, count);
+	WARN(skipped, "%lu pages are still in use!\n", skipped);
 
 	return true;
 }
Signature · 1 line
2.55.0
#1 lore raw
Quoted 4 lines · Rik van Riel

This change should be safe because nothing can get reallocated while it
is still in use.

And of course Sashiko found something I overlooked.

I'll spin a v2 tomorrow.

https://sashiko.dev/#/patchset/20260809210608.06b5ccb9@fangorn

The hugetlb CMA code uses order 9 as the smallest size that may be returned to the CMA allocator, which I suppose works because when mTHPs, THPs, and other movable large folios end up in the hugetlb CMA area, those do not go through the CMA allocator, and never touch the bitmap?

That additional bitmap alongside of the pages and page blocks looks like trouble.