diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-05-04 18:26:13 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2016-05-04 18:26:13 +0000 |
commit | b7964a1c70be9a48dfcff83ed2e2ee6126acf550 (patch) | |
tree | d909e784760123b92a66669aef5de5822753da6b /sys/arch/sparc64 | |
parent | 58f40ee78f1a5b2537df8b491bd70caf38054474 (diff) |
Some hardware (such as the onboard dc(4) of the Netra X1) has a broken DMA
engine that might attempt to read beyond the end of the buffer that was
programmed. The IOMMU catches this "DMA overrun" and throws an unrecoverable
error at us, at which point we have no choice but to panic. To avoid this
implement a BUS_DMA_OVERRUN flag that maps an additional scratch page at the
end of the vdma address range. DMA requests will spill over into this page,
which just returns zeroes.
Thanks to matthieu@ for giving me access to a machine with the problem.
ok deraadt@, beck@
Diffstat (limited to 'sys/arch/sparc64')
-rw-r--r-- | sys/arch/sparc64/dev/iommu.c | 23 | ||||
-rw-r--r-- | sys/arch/sparc64/dev/iommuvar.h | 4 | ||||
-rw-r--r-- | sys/arch/sparc64/dev/viommu.c | 26 | ||||
-rw-r--r-- | sys/arch/sparc64/include/bus.h | 29 |
4 files changed, 65 insertions, 17 deletions
diff --git a/sys/arch/sparc64/dev/iommu.c b/sys/arch/sparc64/dev/iommu.c index 13f48d22fbb..66316b1844c 100644 --- a/sys/arch/sparc64/dev/iommu.c +++ b/sys/arch/sparc64/dev/iommu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: iommu.c,v 1.72 2015/01/09 14:23:25 kettenis Exp $ */ +/* $OpenBSD: iommu.c,v 1.73 2016/05/04 18:26:12 kettenis Exp $ */ /* $NetBSD: iommu.c,v 1.47 2002/02/08 20:03:45 eeh Exp $ */ /* @@ -195,6 +195,13 @@ iommu_init(char *name, struct iommu_state *is, int tsbsize, u_int32_t iovabase) pmap_update(pmap_kernel()); memset(is->is_tsb, 0, size); + TAILQ_INIT(&mlist); + if (uvm_pglistalloc(PAGE_SIZE, 0, -1, PAGE_SIZE, 0, &mlist, 1, + UVM_PLA_NOWAIT | UVM_PLA_ZERO) != 0) + panic("%s: no memory", __func__); + 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_dma_tag_t t0, bus_dmamap_t map, } } } + 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); @@ -941,6 +955,13 @@ iommu_dvmamap_load_raw(bus_dma_tag_t t, bus_dma_tag_t t0, bus_dmamap_t map, 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; mtx_enter(&is->is_mtx); diff --git a/sys/arch/sparc64/dev/iommuvar.h b/sys/arch/sparc64/dev/iommuvar.h index cc0dab203c9..f0a548079c6 100644 --- a/sys/arch/sparc64/dev/iommuvar.h +++ b/sys/arch/sparc64/dev/iommuvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: iommuvar.h,v 1.16 2014/01/22 10:52:35 kettenis Exp $ */ +/* $OpenBSD: iommuvar.h,v 1.17 2016/05/04 18:26:12 kettenis Exp $ */ /* $NetBSD: iommuvar.h,v 1.9 2001/10/07 20:30:41 eeh Exp $ */ /* @@ -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 */ diff --git a/sys/arch/sparc64/dev/viommu.c b/sys/arch/sparc64/dev/viommu.c index 2b95a707488..77362d76eeb 100644 --- a/sys/arch/sparc64/dev/viommu.c +++ b/sys/arch/sparc64/dev/viommu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: viommu.c,v 1.16 2015/01/09 14:23:25 kettenis Exp $ */ +/* $OpenBSD: viommu.c,v 1.17 2016/05/04 18:26:12 kettenis Exp $ */ /* $NetBSD: iommu.c,v 1.47 2002/02/08 20:03:45 eeh Exp $ */ /* @@ -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_state *is, int tsbsize, is->is_dvmaend = iovabase + IOTSB_VSIZE(tsbsize) - 1; } + TAILQ_INIT(&mlist); + if (uvm_pglistalloc(PAGE_SIZE, 0, -1, PAGE_SIZE, 0, &mlist, 1, + UVM_PLA_NOWAIT | UVM_PLA_ZERO) != 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_dma_tag_t t0, bus_dmamap_t map, } } } + 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); @@ -524,6 +541,13 @@ viommu_dvmamap_load_raw(bus_dma_tag_t t, bus_dma_tag_t t0, bus_dmamap_t map, 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; mtx_enter(&is->is_mtx); diff --git a/sys/arch/sparc64/include/bus.h b/sys/arch/sparc64/include/bus.h index bf016a2ab40..a7a88034e0f 100644 --- a/sys/arch/sparc64/include/bus.h +++ b/sys/arch/sparc64/include/bus.h @@ -1,4 +1,4 @@ -/* $OpenBSD: bus.h,v 1.29 2013/05/13 17:46:42 kettenis Exp $ */ +/* $OpenBSD: bus.h,v 1.30 2016/05/04 18:26:12 kettenis Exp $ */ /* $NetBSD: bus.h,v 1.31 2001/09/21 15:30:41 wiz Exp $ */ /*- @@ -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 */ |