It is pretty clear that the DMA engine on the Davicom dc(4) hardware
is broken and will read beyond the end of the buffer that we pass it.
This is bad news for hardware that uses an IOMMU, as it will detect
the DMA overrun and (at least on sparc64) signal an unrecoverable
error.
It is somewhat difficult to fix this in the driver. We could copy
mbufs that we want to transmit into a buffer that has enough trailing
space, but then we have to make sure that the buffer is physically
contiguous and that the unused trailing space is mapped into the
IOMMU.
The diff below takes a different approach. It introduces a new
BUS_DMA_OVERRUN flag. On sparc64 this flag will make sure that we map
an additional scratch page into the IOMMU. This fixes the DMA errors
that people reported ever since dlg@ changed the pools.
If people agree with this approach, I will add a
#define BUS_DMA_OVERRUN 0
to all the other bus_dma implementations in the tree.
Thoughts?
Index: dev/ic/dc.c
===================================================================
RCS file: /cvs/src/sys/dev/ic/dc.c,v
retrieving revision 1.149
diff -u -p -r1.149 dc.c
--- dev/ic/dc.c 28 Nov 2015 22:57:43 -0000 1.149
+++ dev/ic/dc.c 2 Apr 2016 15:42:15 -0000
@@ -2584,13 +2584,13 @@ dc_start(struct ifnet *ifp)
map = sc->sc_tx_sparemap;
switch (bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
- BUS_DMA_NOWAIT)) {
+ BUS_DMA_NOWAIT | BUS_DMA_OVERRUN)) {
case 0:
break;
case EFBIG:
if (m_defrag(m, M_DONTWAIT) == 0 &&
bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
- BUS_DMA_NOWAIT) == 0)
+ BUS_DMA_NOWAIT | BUS_DMA_OVERRUN) == 0)
break;
/* FALLTHROUGH */
Index: arch/sparc64/dev/iommu.c
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/dev/iommu.c,v
retrieving revision 1.72
diff -u -p -r1.72 iommu.c
--- arch/sparc64/dev/iommu.c 9 Jan 2015 14:23:25 -0000 1.72
+++ arch/sparc64/dev/iommu.c 2 Apr 2016 15:42:17 -0000
@@ -195,6 +195,13 @@ iommu_init(char *name, struct iommu_stat
pmap_update(pmap_kernel());
memset(is->is_tsb, 0, size);
+ TAILQ_INIT(&mlist);
+ if (uvm_pglistalloc(PAGE_SIZE, (paddr_t)0, (paddr_t)-1,
+ (paddr_t)PAGE_SIZE, (paddr_t)0, &mlist, 1, UVM_PLA_NOWAIT) != 0)
+ panic("iommu_init: no memory");
+ m = TAILQ_FIRST(&mlist);
+ is->is_scratch = VM_PAGE_TO_PHYS(m);
+
#ifdef DEBUG
if (iommudebug & IDB_INFO) {
/* Probe the iommu */
@@ -734,6 +741,13 @@ iommu_dvmamap_load(bus_dma_tag_t t, bus_
}
}
}
+ if (flags & BUS_DMA_OVERRUN) {
+ err = iommu_iomap_insert_page(ims, is->is_scratch);
+ if (err) {
+ iommu_iomap_clear_pages(ims);
+ return (EFBIG);
+ }
+ }
sgsize = ims->ims_map.ipm_pagecnt * PAGE_SIZE;
mtx_enter(&is->is_mtx);
@@ -939,6 +953,13 @@ iommu_dvmamap_load_raw(bus_dma_tag_t t,
}
left -= seg_len;
+ }
+ }
+ if (flags & BUS_DMA_OVERRUN) {
+ err = iommu_iomap_insert_page(ims, is->is_scratch);
+ if (err) {
+ iommu_iomap_clear_pages(ims);
+ return (EFBIG);
}
}
sgsize = ims->ims_map.ipm_pagecnt * PAGE_SIZE;
Index: arch/sparc64/dev/iommuvar.h
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/dev/iommuvar.h,v
retrieving revision 1.16
diff -u -p -r1.16 iommuvar.h
--- arch/sparc64/dev/iommuvar.h 22 Jan 2014 10:52:35 -0000 1.16
+++ arch/sparc64/dev/iommuvar.h 2 Apr 2016 15:42:17 -0000
@@ -117,6 +117,8 @@ struct iommu_state {
struct strbuf_ctl *is_sb[2]; /* Streaming buffers if any */
+ paddr_t is_scratch; /* Scratch page */
+
/* copies of our parents state, to allow us to be self contained */
bus_space_tag_t is_bustag; /* our bus tag */
bus_space_handle_t is_iommu; /* IOMMU registers */
Index: arch/sparc64/dev/viommu.c
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/dev/viommu.c,v
retrieving revision 1.16
diff -u -p -r1.16 viommu.c
--- arch/sparc64/dev/viommu.c 9 Jan 2015 14:23:25 -0000 1.16
+++ arch/sparc64/dev/viommu.c 2 Apr 2016 15:42:17 -0000
@@ -106,6 +106,9 @@ void
viommu_init(char *name, struct iommu_state *is, int tsbsize,
u_int32_t iovabase)
{
+ struct vm_page *m;
+ struct pglist mlist;
+
/*
* Setup the iommu.
*
@@ -121,6 +124,13 @@ viommu_init(char *name, struct iommu_sta
is->is_dvmaend = iovabase + IOTSB_VSIZE(tsbsize) - 1;
}
+ TAILQ_INIT(&mlist);
+ if (uvm_pglistalloc(PAGE_SIZE, (paddr_t)0, (paddr_t)-1,
+ (paddr_t)PAGE_SIZE, (paddr_t)0, &mlist, 1, UVM_PLA_NOWAIT) != 0)
+ panic("%s: no memory", __func__);
+ m = TAILQ_FIRST(&mlist);
+ is->is_scratch = VM_PAGE_TO_PHYS(m);
+
/*
* Allocate a dvma map.
*/
@@ -341,6 +351,13 @@ viommu_dvmamap_load(bus_dma_tag_t t, bus
}
}
}
+ if (flags & BUS_DMA_OVERRUN) {
+ err = iommu_iomap_insert_page(ims, is->is_scratch);
+ if (err) {
+ iommu_iomap_clear_pages(ims);
+ return (EFBIG);
+ }
+ }
sgsize = ims->ims_map.ipm_pagecnt * PAGE_SIZE;
mtx_enter(&is->is_mtx);
@@ -522,6 +539,13 @@ viommu_dvmamap_load_raw(bus_dma_tag_t t,
}
left -= seg_len;
+ }
+ }
+ if (flags & BUS_DMA_OVERRUN) {
+ err = iommu_iomap_insert_page(ims, is->is_scratch);
+ if (err) {
+ iommu_iomap_clear_pages(ims);
+ return (EFBIG);
}
}
sgsize = ims->ims_map.ipm_pagecnt * PAGE_SIZE;
Index: arch/sparc64/include/bus.h
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/include/bus.h,v
retrieving revision 1.29
diff -u -p -r1.29 bus.h
--- arch/sparc64/include/bus.h 13 May 2013 17:46:42 -0000 1.29
+++ arch/sparc64/include/bus.h 2 Apr 2016 15:42:17 -0000
@@ -356,19 +356,20 @@ bus_space_barrier(t, h, o, s, f)
/*
* Flags used in various bus DMA methods.
*/
-#define BUS_DMA_WAITOK 0x000 /* safe to sleep (pseudo-flag)
*/
-#define BUS_DMA_NOWAIT 0x001 /* not safe to sleep */
-#define BUS_DMA_ALLOCNOW 0x002 /* perform resource allocation
now */
-#define BUS_DMA_COHERENT 0x004 /* hint: map memory DMA
coherent */
-#define BUS_DMA_NOWRITE 0x008 /* I suppose the following two
should default on */
-#define BUS_DMA_BUS1 0x010 /* placeholders for bus
functions... */
-#define BUS_DMA_BUS2 0x020
-#define BUS_DMA_BUS3 0x040
-#define BUS_DMA_BUS4 0x080
-#define BUS_DMA_STREAMING 0x100 /* hint: sequential,
unidirectional */
-#define BUS_DMA_READ 0x200 /* mapping is device -> memory
only */
-#define BUS_DMA_WRITE 0x400 /* mapping is memory -> device
only */
-#define BUS_DMA_ZERO 0x800 /* zero memory in dmamem_alloc
*/
+#define BUS_DMA_WAITOK 0x0000 /* safe to sleep (pseudo-flag)
*/
+#define BUS_DMA_NOWAIT 0x0001 /* not safe to sleep */
+#define BUS_DMA_ALLOCNOW 0x0002 /* perform resource allocation
now */
+#define BUS_DMA_COHERENT 0x0004 /* hint: map memory DMA
coherent */
+#define BUS_DMA_NOWRITE 0x0008 /* I suppose the following two
should default on */
+#define BUS_DMA_BUS1 0x0010 /* placeholders for bus
functions... */
+#define BUS_DMA_BUS2 0x0020
+#define BUS_DMA_BUS3 0x0040
+#define BUS_DMA_BUS4 0x0080
+#define BUS_DMA_STREAMING 0x0100 /* hint: sequential,
unidirectional */
+#define BUS_DMA_READ 0x0200 /* mapping is device -> memory
only */
+#define BUS_DMA_WRITE 0x0400 /* mapping is memory -> device
only */
+#define BUS_DMA_ZERO 0x0800 /* zero memory in dmamem_alloc
*/
+#define BUS_DMA_OVERRUN 0x1000 /* tolerate DMA overruns */
#define BUS_DMA_NOCACHE BUS_DMA_BUS1
#define BUS_DMA_DVMA BUS_DMA_BUS2 /* Don't bother with
alignment */