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