Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/arch/alpha/common Deal with a scenario where:



details: https://anonhg.NetBSD.org/src/rev/a499b627f28e
branches: trunk
changeset: 379876:a499b627f28e
user: thorpej <thorpej%NetBSD.org@localhost>
date: Thu Jun 24 16:41:16 2021 +0000
description:
Deal with a scenario where:
- DMA map has a boundary constraint.
- Caller asks us to map a buffer that's exactly the same size as the
 boundary constraint, but is not page-aligned.
This results in the size being larger than the boundary constraint after
page-rounding, and and vmem_xalloc() fires a KASSERT for it. This is
easy to trigger by running fsck.
We handle this by detecting the condition and creating an extra DMA
segment for it the spill-over.
diffstat:
 sys/arch/alpha/common/sgmap_typedep.c | 157 +++++++++++++++++++++++++++++----
 1 files changed, 137 insertions(+), 20 deletions(-)
diffs (275 lines):
diff -r 86297cad032f -r a499b627f28e sys/arch/alpha/common/sgmap_typedep.c
--- a/sys/arch/alpha/common/sgmap_typedep.c Thu Jun 24 15:41:25 2021 +0000
+++ b/sys/arch/alpha/common/sgmap_typedep.c Thu Jun 24 16:41:16 2021 +0000
@@ -1,4 +1,4 @@
-/* $NetBSD: sgmap_typedep.c,v 1.41 2021年04月15日 00:11:09 rin Exp $ */
+/* $NetBSD: sgmap_typedep.c,v 1.42 2021年06月24日 16:41:16 thorpej Exp $ */
 
 /*-
 * Copyright (c) 1997, 1998, 2001 The NetBSD Foundation, Inc.
@@ -31,7 +31,7 @@
 */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: sgmap_typedep.c,v 1.41 2021年04月15日 00:11:09 rin Exp $");
+__KERNEL_RCSID(1, "$NetBSD: sgmap_typedep.c,v 1.42 2021年06月24日 16:41:16 thorpej Exp $");
 
 #include "opt_ddb.h"
 
@@ -60,23 +60,31 @@ void
 }
 
 DMA_COUNT_DECL(spill_page);
+DMA_COUNT_DECL(extra_segment);
+DMA_COUNT_DECL(extra_segment_and_spill);
 
 static int
 __C(SGMAP_TYPE,_load_buffer)(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
- size_t buflen, struct vmspace *vm, int flags, int seg,
+ size_t buflen, struct vmspace *vm, int flags, int * const segp,
 struct alpha_sgmap *sgmap)
 {
 vaddr_t endva, va = (vaddr_t)buf;
 paddr_t pa;
- bus_addr_t dmaoffset, sgva;
- bus_size_t sgvalen, boundary, alignment;
+ bus_addr_t dmaoffset, sgva, extra_sgva;
+ bus_size_t sgvalen, extra_sgvalen, boundary, alignment;
 SGMAP_PTE_TYPE *pte, *page_table = sgmap->aps_pt;
- int pteidx, error, spill;
+ int pteidx, error, spill, seg = *segp;
 
 /* Initialize the spill page PTE if it hasn't been already. */
 if (__C(SGMAP_TYPE,_prefetch_spill_page_pte) == 0)
 __C(SGMAP_TYPE,_init_spill_page_pte)();
 
+ if (seg == map->_dm_segcnt) {
+ /* Ran of segments. */
+ return EFBIG;
+ }
+ KASSERT(seg < map->_dm_segcnt);
+
 /*
 * Remember the offset into the first page and the total
 * transfer length.
@@ -106,13 +114,77 @@ static int
 else
 spill = 0;
 
+ boundary = map->_dm_boundary;
+
+ /*
+ * Caller's mistake if the requested length is larger than
+ * their own boundary constraint.
+ */
+ if (__predict_false(boundary != 0 && buflen > boundary)) {
+ return EINVAL;
+ }
+
 endva = round_page(va + buflen);
 va = trunc_page(va);
 
- boundary = map->_dm_boundary;
+ const vm_flag_t vmflags = VM_INSTANTFIT |
+ ((flags & BUS_DMA_NOWAIT) ? VM_NOSLEEP : VM_SLEEP);
+
 alignment = PAGE_SIZE;
+ sgvalen = (endva - va);
 
- sgvalen = (endva - va);
+ SGMAP_PTE_TYPE spill_pte_v = __C(SGMAP_TYPE,_prefetch_spill_page_pte);
+
+ /*
+ * If we have a boundary constraint, it's possible to end up in
+ * a situation where sgvalen > boundary if the caller's buffer
+ * is not page aligned. In this case, we will have to allocate
+ * an extra SG segment and split the buffer.
+ */
+ if (__predict_false(boundary != 0 && boundary < sgvalen)) {
+#ifdef SGMAP_DEBUG
+ if (__C(SGMAP_TYPE,_debug)) {
+ printf("sgmap_load: extra segment needed\n");
+ }
+#endif
+ DMA_COUNT(extra_segment);
+
+ /* This should only ever happen for unaligned buffers. */
+ KASSERT(dmaoffset != 0);
+
+ extra_sgvalen = sgvalen - boundary;
+ KASSERT(extra_sgvalen == PAGE_SIZE);
+
+ /*
+ * Adjust the lengths of the first segment. The length
+ * of the second segment will be dmaoffset.
+ */
+ sgvalen -= extra_sgvalen;
+ endva -= extra_sgvalen;
+ buflen -= dmaoffset;
+
+ if (spill) {
+ DMA_COUNT(extra_segment_and_spill);
+ extra_sgvalen += PAGE_SIZE;
+ }
+
+ error = vmem_xalloc(sgmap->aps_arena, extra_sgvalen,
+ alignment, /* alignment */
+ 0, /* phase */
+ boundary, /* nocross */
+ VMEM_ADDR_MIN, /* minaddr */
+ VMEM_ADDR_MAX, /* maxaddr */
+ vmflags,
+ &extra_sgva);
+ if (error) {
+ return error;
+ }
+ } else {
+ extra_sgvalen = 0;
+ extra_sgva = 0;
+ }
+
+
 if (spill) {
 DMA_COUNT(spill_page);
 sgvalen += PAGE_SIZE;
@@ -120,6 +192,11 @@ static int
 /*
 * ARGH! If the addition of the spill page bumped us
 * over our boundary, we have to 2x the boundary limit.
+ * To compensate (and enforce the original boundary
+ * constraint), we force our alignment to be the previous
+ * boundary, thus ensuring that the only boundary violation
+ * is the pre-fetch that the SGMAP controller performs that
+ * necessitates the spill page in the first place.
 */
 if (boundary && boundary < sgvalen) {
 alignment = boundary;
@@ -137,9 +214,6 @@ static int
 }
 #endif
 
- const vm_flag_t vmflags = VM_INSTANTFIT |
- ((flags & BUS_DMA_NOWAIT) ? VM_NOSLEEP : VM_SLEEP);
-
 error = vmem_xalloc(sgmap->aps_arena, sgvalen,
 alignment, /* alignment */
 0, /* phase */
@@ -148,8 +222,12 @@ static int
 VMEM_ADDR_MAX, /* maxaddr */
 vmflags,
 &sgva);
- if (error)
- return (error);
+ if (error) {
+ if (extra_sgvalen != 0) {
+ vmem_xfree(sgmap->aps_arena, extra_sgva, extra_sgvalen);
+ }
+ return error;
+ }
 
 pteidx = sgva >> SGMAP_ADDR_PTEIDX_SHIFT;
 pte = &page_table[pteidx * SGMAP_PTE_SPACING];
@@ -164,6 +242,17 @@ static int
 /* Generate the DMA address. */
 map->dm_segs[seg].ds_addr = sgmap->aps_wbase | sgva | dmaoffset;
 map->dm_segs[seg].ds_len = buflen;
+ if (__predict_false(extra_sgvalen != 0)) {
+ if (++seg == map->_dm_segcnt) {
+ /* Boo! Ran out of segments! */
+ vmem_xfree(sgmap->aps_arena, extra_sgva, extra_sgvalen);
+ vmem_xfree(sgmap->aps_arena, sgva, sgvalen);
+ return EFBIG;
+ }
+ map->dm_segs[seg].ds_addr = sgmap->aps_wbase | extra_sgva;
+ map->dm_segs[seg].ds_len = dmaoffset;
+ *segp = seg;
+ }
 
 #ifdef SGMAP_DEBUG
 if (__C(SGMAP_TYPE,_debug))
@@ -189,9 +278,37 @@ static int
 #endif
 }
 
+ if (__predict_false(extra_sgvalen != 0)) {
+ int extra_pteidx = extra_sgva >> SGMAP_ADDR_PTEIDX_SHIFT;
+ SGMAP_PTE_TYPE *extra_pte =
+ &page_table[extra_pteidx * SGMAP_PTE_SPACING];
+
+ /* va == endva == address of extra page */
+ KASSERT(va == endva);
+ if (!VMSPACE_IS_KERNEL_P(vm))
+ (void) pmap_extract(vm->vm_map.pmap, va, &pa);
+ else
+ pa = vtophys(va);
+
+ /*
+ * If a spill page is needed, the previous segment will
+ * need to use this PTE value for it.
+ */
+ spill_pte_v = (pa >> SGPTE_PGADDR_SHIFT) | SGPTE_VALID;
+ *extra_pte = spill_pte_v;
+
+ /* ...but the extra segment uses the real spill PTE. */
+ if (spill) {
+ extra_pteidx++;
+ extra_pte =
+ &page_table[extra_pteidx * SGMAP_PTE_SPACING];
+ *extra_pte = __C(SGMAP_TYPE,_prefetch_spill_page_pte);
+ }
+ }
+
 if (spill) {
 /* ...and the prefetch-spill page. */
- *pte = __C(SGMAP_TYPE,_prefetch_spill_page_pte);
+ *pte = spill_pte_v;
 #ifdef SGMAP_DEBUG
 if (__C(SGMAP_TYPE,_debug)) {
 printf("sgmap_load: spill page, pte = %p, "
@@ -235,7 +352,7 @@ int
 }
 seg = 0;
 error = __C(SGMAP_TYPE,_load_buffer)(t, map, buf, buflen, vm,
- flags, seg, sgmap);
+ flags, &seg, sgmap);
 
 alpha_mb();
 
@@ -247,7 +364,7 @@ int
 if (error == 0) {
 DMA_COUNT(load);
 map->dm_mapsize = buflen;
- map->dm_nsegs = 1;
+ map->dm_nsegs = seg + 1;
 map->_dm_window = t;
 } else {
 map->_dm_flags &= ~(BUS_DMA_READ|BUS_DMA_WRITE);
@@ -297,7 +414,7 @@ int
 if (m->m_len == 0)
 continue;
 error = __C(SGMAP_TYPE,_load_buffer)(t, map,
- m->m_data, m->m_len, vmspace_kernel(), flags, seg, sgmap);
+ m->m_data, m->m_len, vmspace_kernel(), flags, &seg, sgmap);
 seg++;
 }
 
@@ -361,8 +478,7 @@ int
 
 seg = 0;
 error = 0;
- for (i = 0; i < uio->uio_iovcnt && resid != 0 && error == 0;
- i++, seg++) {
+ for (i = 0; i < uio->uio_iovcnt && resid != 0 && error == 0; i++) {
 /*
 * Now at the first iovec to load. Load each iovec
 * until we have exhausted the residual count.
@@ -371,7 +487,8 @@ int
 addr = (void *)iov[i].iov_base;
 
 error = __C(SGMAP_TYPE,_load_buffer)(t, map,
- addr, minlen, vm, flags, seg, sgmap);
+ addr, minlen, vm, flags, &seg, sgmap);
+ seg++;
 
 resid -= minlen;
 }


Home | Main Index | Thread Index | Old Index

AltStyle によって変換されたページ (->オリジナル) /