git: 0a40193a6e0f - stable/14 - busdma: fix page miscount for small segment sizes

From: Mitchell Horne <mhorne_at_FreeBSD.org>
Date: Thu, 18 Apr 2024 18:12:24 UTC
The branch stable/14 has been updated by mhorne:

URL: https://cgit.FreeBSD.org/src/commit/?id=0a40193a6e0fdf73b618aec3bccfb05bd1d0ddf0

commit 0a40193a6e0fdf73b618aec3bccfb05bd1d0ddf0
Author:     Mitchell Horne <mhorne@FreeBSD.org>
AuthorDate: 2021-05-25 21:04:56 +0000
Commit:     Mitchell Horne <mhorne@FreeBSD.org>
CommitDate: 2024-04-18 18:09:02 +0000

    busdma: fix page miscount for small segment sizes
    
    For small segments (< PAGE_SIZE) there is a mismatch between how
    required bounce pages are counted in _bus_dmamap_count_pages() and
    bounce_bus_dmamap_load_buffer().
    
    This problem has been observed on the RISC-V VisionFive v2 SoC (and
    earlier revisions of the hardware) which has memory physically addressed
    above 4GB. This requires some bouncing for the dwmmc driver, which has
    has a maximum segment size of 2048 bytes. When attempting to load a
    page-aligned 4-page buffer that requires bouncing, we can end up
    counting 4 bounce pages for an 8-segment transfer. These pages will be
    incorrectly configured to cover only the first half of the transfer (4 x
    2048 bytes).
    
    Fix the immediate issue by adding the maxsegsz check to
    _bus_dmamap_count_pages(); this is what _bus_dmamap_count_phys() does
    already. The result is that we will inefficiently allocate a separate
    bounce page for each segment (8 pages for the example above), but the
    transfer will proceed in its entirety.
    
    The more complete fix is to address the shortcomings in how small
    segments are assigned to bounce pages, so that we opportunistically
    batch multiple segments to a page whenever they fit (e.g. two 2048 bytes
    segments per 4096 page). This will be addressed more holistically in the
    future. For now this change will prevent the (silent) incomplete
    transfers that have been observed.
    
    PR:             273694
    Reported by:    Jari Sihvola <jsihv@gmx.com>
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D34118
    
    (cherry picked from commit b134c10d658c3b350e04aa8dbd2628e955ddcce0)
---
 sys/arm/arm/busdma_machdep.c         | 11 ++++++-----
 sys/arm64/arm64/busdma_bounce.c      |  1 +
 sys/powerpc/powerpc/busdma_machdep.c |  1 +
 sys/riscv/riscv/busdma_bounce.c      |  1 +
 sys/x86/x86/busdma_bounce.c          |  1 +
 5 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/sys/arm/arm/busdma_machdep.c b/sys/arm/arm/busdma_machdep.c
index a4f2c803f30a..a24dcf94fe08 100644
--- a/sys/arm/arm/busdma_machdep.c
+++ b/sys/arm/arm/busdma_machdep.c
@@ -873,6 +873,7 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, pmap_t pmap, bus_dmamap_t map,
 	vm_offset_t vaddr;
 	vm_offset_t vendaddr;
 	bus_addr_t paddr;
+	bus_size_t sg_len;
 
 	if (map->pagesneeded == 0) {
 		CTR5(KTR_BUSDMA, "lowaddr= %d, boundary= %d, alignment= %d"
@@ -887,16 +888,16 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, pmap_t pmap, bus_dmamap_t map,
 		vendaddr = (vm_offset_t)buf + buflen;
 
 		while (vaddr < vendaddr) {
+			sg_len = MIN(vendaddr - vaddr,
+			    (PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK)));
+			sg_len = MIN(sg_len, dmat->maxsegsz);
 			if (__predict_true(pmap == kernel_pmap))
 				paddr = pmap_kextract(vaddr);
 			else
 				paddr = pmap_extract(pmap, vaddr);
-			if (must_bounce(dmat, map, paddr,
-			    min(vendaddr - vaddr, (PAGE_SIZE - ((vm_offset_t)vaddr &
-			    PAGE_MASK)))) != 0) {
+			if (must_bounce(dmat, map, paddr, sg_len) != 0)
 				map->pagesneeded++;
-			}
-			vaddr += (PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK));
+			vaddr += sg_len;
 		}
 		CTR1(KTR_BUSDMA, "pagesneeded= %d", map->pagesneeded);
 	}
diff --git a/sys/arm64/arm64/busdma_bounce.c b/sys/arm64/arm64/busdma_bounce.c
index c1028f35ba7f..cd7655300dc4 100644
--- a/sys/arm64/arm64/busdma_bounce.c
+++ b/sys/arm64/arm64/busdma_bounce.c
@@ -717,6 +717,7 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, bus_dmamap_t map, pmap_t pmap,
 
 		while (vaddr < vendaddr) {
 			sg_len = PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK);
+			sg_len = MIN(sg_len, dmat->common.maxsegsz);
 			if (pmap == kernel_pmap)
 				paddr = pmap_kextract(vaddr);
 			else
diff --git a/sys/powerpc/powerpc/busdma_machdep.c b/sys/powerpc/powerpc/busdma_machdep.c
index 873a67458dfc..6b70c0b34ede 100644
--- a/sys/powerpc/powerpc/busdma_machdep.c
+++ b/sys/powerpc/powerpc/busdma_machdep.c
@@ -573,6 +573,7 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, bus_dmamap_t map, pmap_t pmap,
 			bus_size_t sg_len;
 
 			sg_len = PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK);
+			sg_len = MIN(sg_len, dmat->maxsegsz);
 			if (pmap == kernel_pmap)
 				paddr = pmap_kextract(vaddr);
 			else
diff --git a/sys/riscv/riscv/busdma_bounce.c b/sys/riscv/riscv/busdma_bounce.c
index 6b50dc527b66..dcd287806eaf 100644
--- a/sys/riscv/riscv/busdma_bounce.c
+++ b/sys/riscv/riscv/busdma_bounce.c
@@ -552,6 +552,7 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, bus_dmamap_t map, pmap_t pmap,
 
 		while (vaddr < vendaddr) {
 			sg_len = PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK);
+			sg_len = MIN(sg_len, dmat->common.maxsegsz);
 			if (pmap == kernel_pmap)
 				paddr = pmap_kextract(vaddr);
 			else
diff --git a/sys/x86/x86/busdma_bounce.c b/sys/x86/x86/busdma_bounce.c
index b9943ad3792e..c60e239a5109 100644
--- a/sys/x86/x86/busdma_bounce.c
+++ b/sys/x86/x86/busdma_bounce.c
@@ -562,6 +562,7 @@ _bus_dmamap_count_pages(bus_dma_tag_t dmat, bus_dmamap_t map, pmap_t pmap,
 
 		while (vaddr < vendaddr) {
 			sg_len = PAGE_SIZE - ((vm_offset_t)vaddr & PAGE_MASK);
+			sg_len = MIN(sg_len, dmat->common.maxsegsz);
 			if (pmap == kernel_pmap)
 				paddr = pmap_kextract(vaddr);
 			else