#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bus_dma.c,v 1.92 2025/08/04 11:53:52 skrll Exp $");
#include "ioapic.h"
#include "isa.h"
#include "opt_mpbios.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/asan.h>
#include <sys/msan.h>
#include <sys/bus.h>
#include <machine/bus_private.h>
#if NIOAPIC > 0
#include <machine/i82093var.h>
#endif
#ifdef MPBIOS
#include <machine/mpbiosvar.h>
#endif
#include <machine/pmap_private.h>
#if NISA > 0
#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>
#endif
#include <uvm/uvm.h>
extern paddr_t avail_end;
#define IDTVEC(name) __CONCAT(X,name)
typedef void (vector)(void);
extern vector *IDTVEC(intr)[];
#define BUSDMA_BOUNCESTATS
#ifdef BUSDMA_BOUNCESTATS
#define BUSDMA_EVCNT_DECL(name) \
static struct evcnt bus_dma_ev_##name = \
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "bus_dma", #name); \
EVCNT_ATTACH_STATIC(bus_dma_ev_##name)
#define STAT_INCR(name) \
bus_dma_ev_##name.ev_count++
#define STAT_DECR(name) \
bus_dma_ev_##name.ev_count--
BUSDMA_EVCNT_DECL(nbouncebufs);
BUSDMA_EVCNT_DECL(loads);
BUSDMA_EVCNT_DECL(bounces);
#else
#define STAT_INCR(x)
#define STAT_DECR(x)
#endif
static int _bus_dmamap_create(bus_dma_tag_t, bus_size_t, int, bus_size_t,
bus_size_t, int, bus_dmamap_t *);
static void _bus_dmamap_destroy(bus_dma_tag_t, bus_dmamap_t);
static int _bus_dmamap_load(bus_dma_tag_t, bus_dmamap_t, void *,
bus_size_t, struct proc *, int);
static int _bus_dmamap_load_mbuf(bus_dma_tag_t, bus_dmamap_t,
struct mbuf *, int);
static int _bus_dmamap_load_uio(bus_dma_tag_t, bus_dmamap_t,
struct uio *, int);
static int _bus_dmamap_load_raw(bus_dma_tag_t, bus_dmamap_t,
bus_dma_segment_t *, int, bus_size_t, int);
static void _bus_dmamap_unload(bus_dma_tag_t, bus_dmamap_t);
static void _bus_dmamap_sync(bus_dma_tag_t, bus_dmamap_t, bus_addr_t,
bus_size_t, int);
static int _bus_dmamem_alloc(bus_dma_tag_t tag, bus_size_t size,
bus_size_t alignment, bus_size_t boundary,
bus_dma_segment_t *segs, int nsegs, int *rsegs, int flags);
static void _bus_dmamem_free(bus_dma_tag_t tag, bus_dma_segment_t *segs,
int nsegs);
static int _bus_dmamem_map(bus_dma_tag_t tag, bus_dma_segment_t *segs,
int nsegs, size_t size, void **kvap, int flags);
static void _bus_dmamem_unmap(bus_dma_tag_t tag, void *kva, size_t size);
static paddr_t _bus_dmamem_mmap(bus_dma_tag_t tag, bus_dma_segment_t *segs,
int nsegs, off_t off, int prot, int flags);
static int _bus_dmatag_subregion(bus_dma_tag_t tag, bus_addr_t min_addr,
bus_addr_t max_addr, bus_dma_tag_t *newtag, int flags);
static void _bus_dmatag_destroy(bus_dma_tag_t tag);
static int _bus_dma_uiomove(void *, struct uio *, size_t, int);
static int _bus_dma_alloc_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map,
bus_size_t size, int flags);
static void _bus_dma_free_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map);
static int _bus_dmamap_load_buffer(bus_dma_tag_t t, bus_dmamap_t map,
void *buf, bus_size_t buflen, struct vmspace *vm, int flags);
static int _bus_dmamap_load_busaddr(bus_dma_tag_t, bus_dmamap_t,
bus_addr_t, bus_size_t);
#ifndef _BUS_DMAMEM_ALLOC_RANGE
static int _bus_dmamem_alloc_range(bus_dma_tag_t tag, bus_size_t size,
bus_size_t alignment, bus_size_t boundary,
bus_dma_segment_t *segs, int nsegs, int *rsegs, int flags,
bus_addr_t low, bus_addr_t high);
#define _BUS_DMAMEM_ALLOC_RANGE _bus_dmamem_alloc_range
static int
_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
{
paddr_t curaddr, lastaddr;
struct vm_page *m;
struct pglist mlist;
int curseg, error;
bus_size_t uboundary;
size = round_page(size);
KASSERTMSG(boundary >= PAGE_SIZE || boundary == 0,
"boundary=0x%"PRIxBUSSIZE, boundary);
if (boundary == 0)
uboundary = 0;
else {
uboundary = boundary;
while (uboundary < size)
uboundary = uboundary << 1;
}
error = uvm_pglistalloc(size, low, high, alignment, uboundary,
&mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
if (error)
return (error);
m = TAILQ_FIRST(&mlist);
curseg = 0;
lastaddr = segs[curseg].ds_addr = VM_PAGE_TO_PHYS(m);
segs[curseg].ds_len = PAGE_SIZE;
m = m->pageq.queue.tqe_next;
for (; m != NULL; m = m->pageq.queue.tqe_next) {
curaddr = VM_PAGE_TO_PHYS(m);
KASSERTMSG(curaddr >= low, "curaddr=%#"PRIxPADDR
" low=%#"PRIxBUSADDR" high=%#"PRIxBUSADDR,
curaddr, low, high);
KASSERTMSG(curaddr < high, "curaddr=%#"PRIxPADDR
" low=%#"PRIxBUSADDR" high=%#"PRIxBUSADDR,
curaddr, low, high);
if (curaddr == (lastaddr + PAGE_SIZE) &&
(lastaddr & boundary) == (curaddr & boundary)) {
segs[curseg].ds_len += PAGE_SIZE;
} else {
curseg++;
KASSERTMSG(curseg < nsegs, "curseg %d size %llx",
curseg, (long long)size);
segs[curseg].ds_addr = curaddr;
segs[curseg].ds_len = PAGE_SIZE;
}
lastaddr = curaddr;
}
*rsegs = curseg + 1;
return (0);
}
#endif
static int
_bus_dmamap_create(bus_dma_tag_t t, bus_size_t size, int nsegments,
bus_size_t maxsegsz, bus_size_t boundary, int flags, bus_dmamap_t *dmamp)
{
struct x86_bus_dma_cookie *cookie;
bus_dmamap_t map;
int error, cookieflags;
void *cookiestore, *mapstore;
size_t cookiesize, mapsize;
error = 0;
mapsize = sizeof(struct x86_bus_dmamap) +
(sizeof(bus_dma_segment_t) * (nsegments - 1));
if ((mapstore = malloc(mapsize, M_DMAMAP, M_ZERO |
((flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK))) == NULL)
return (ENOMEM);
map = (struct x86_bus_dmamap *)mapstore;
map->_dm_size = size;
map->_dm_segcnt = nsegments;
map->_dm_maxmaxsegsz = maxsegsz;
map->_dm_boundary = boundary;
map->_dm_bounce_thresh = t->_bounce_thresh;
map->_dm_flags = flags & ~(BUS_DMA_WAITOK|BUS_DMA_NOWAIT);
map->dm_maxsegsz = maxsegsz;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
if (t->_bounce_thresh == 0 || _BUS_AVAIL_END <= t->_bounce_thresh - 1)
map->_dm_bounce_thresh = 0;
cookieflags = 0;
if (t->_may_bounce != NULL) {
error = t->_may_bounce(t, map, flags, &cookieflags);
if (error != 0)
goto out;
}
if (map->_dm_bounce_thresh != 0)
cookieflags |= X86_DMA_MIGHT_NEED_BOUNCE;
if ((cookieflags & X86_DMA_MIGHT_NEED_BOUNCE) == 0) {
*dmamp = map;
return 0;
}
cookiesize = sizeof(struct x86_bus_dma_cookie) +
(sizeof(bus_dma_segment_t) * map->_dm_segcnt);
if ((cookiestore = malloc(cookiesize, M_DMAMAP, M_ZERO |
((flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK))) == NULL) {
error = ENOMEM;
goto out;
}
cookie = (struct x86_bus_dma_cookie *)cookiestore;
cookie->id_flags = cookieflags;
map->_dm_cookie = cookie;
error = _bus_dma_alloc_bouncebuf(t, map, size, flags);
out:
if (error)
_bus_dmamap_destroy(t, map);
else
*dmamp = map;
return (error);
}
static void
_bus_dmamap_destroy(bus_dma_tag_t t, bus_dmamap_t map)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
if (cookie != NULL) {
if (cookie->id_flags & X86_DMA_HAS_BOUNCE)
_bus_dma_free_bouncebuf(t, map);
free(cookie, M_DMAMAP);
}
free(map, M_DMAMAP);
}
static int
_bus_dmamap_load(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
bus_size_t buflen, struct proc *p, int flags)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
int error;
struct vmspace *vm;
STAT_INCR(loads);
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERTMSG(map->dm_maxsegsz <= map->_dm_maxmaxsegsz,
"maxsegsz=0x%"PRIxBUSSIZE", maxmaxsegsz=0x%"PRIxBUSSIZE,
map->dm_maxsegsz, map->_dm_maxmaxsegsz);
if (buflen > map->_dm_size)
return EINVAL;
if (p != NULL) {
vm = p->p_vmspace;
} else {
vm = vmspace_kernel();
}
error = _bus_dmamap_load_buffer(t, map, buf, buflen, vm, flags);
if (error == 0) {
if (cookie != NULL)
cookie->id_flags &= ~X86_DMA_IS_BOUNCING;
map->dm_mapsize = buflen;
return 0;
}
if (cookie == NULL ||
(cookie->id_flags & X86_DMA_MIGHT_NEED_BOUNCE) == 0)
return error;
STAT_INCR(bounces);
if ((cookie->id_flags & X86_DMA_HAS_BOUNCE) == 0) {
error = _bus_dma_alloc_bouncebuf(t, map, buflen, flags);
if (error)
return (error);
}
cookie->id_origbuf = buf;
cookie->id_origbuflen = buflen;
cookie->id_buftype = X86_DMA_BUFTYPE_LINEAR;
map->dm_nsegs = 0;
error = bus_dmamap_load(t, map, cookie->id_bouncebuf, buflen,
p, flags);
if (error)
return (error);
cookie->id_flags |= X86_DMA_IS_BOUNCING;
return (0);
}
static int
_bus_dmamap_load_busaddr(bus_dma_tag_t t, bus_dmamap_t map,
bus_addr_t addr, bus_size_t size)
{
bus_dma_segment_t * const segs = map->dm_segs;
int nseg = map->dm_nsegs;
bus_addr_t bmask = ~(map->_dm_boundary - 1);
bus_addr_t lastaddr = 0xdead;
bus_size_t sgsize;
if (nseg > 0)
lastaddr = segs[nseg-1].ds_addr + segs[nseg-1].ds_len;
again:
sgsize = size;
if (map->_dm_boundary > 0) {
bus_addr_t baddr;
baddr = (addr + map->_dm_boundary) & bmask;
if (sgsize > (baddr - addr))
sgsize = (baddr - addr);
}
if (nseg > 0 && addr == lastaddr &&
segs[nseg-1].ds_len + sgsize <= map->dm_maxsegsz &&
(map->_dm_boundary == 0 ||
(segs[nseg-1].ds_addr & bmask) == (addr & bmask))) {
segs[nseg-1].ds_len += sgsize;
} else if (nseg >= map->_dm_segcnt) {
return EFBIG;
} else {
segs[nseg].ds_addr = addr;
segs[nseg].ds_len = sgsize;
nseg++;
}
lastaddr = addr + sgsize;
if (map->_dm_bounce_thresh != 0 && lastaddr > map->_dm_bounce_thresh)
return EINVAL;
addr += sgsize;
size -= sgsize;
if (size > 0)
goto again;
map->dm_nsegs = nseg;
return 0;
}
static int
_bus_dmamap_load_mbuf(bus_dma_tag_t t, bus_dmamap_t map, struct mbuf *m0,
int flags)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
int error;
struct mbuf *m;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERTMSG(map->dm_maxsegsz <= map->_dm_maxmaxsegsz,
"maxsegsz=0x%"PRIxBUSSIZE", maxmaxsegsz=0x%"PRIxBUSSIZE,
map->dm_maxsegsz, map->_dm_maxmaxsegsz);
KASSERTMSG(m0->m_flags & M_PKTHDR, "m0=%p m_flags=0x%x", m0,
m0->m_flags);
if (m0->m_pkthdr.len > map->_dm_size)
return (EINVAL);
error = 0;
for (m = m0; m != NULL && error == 0; m = m->m_next) {
int offset;
int remainbytes;
const struct vm_page * const *pgs;
paddr_t paddr;
int size;
if (m->m_len == 0)
continue;
switch (m->m_flags & (M_EXT|M_EXT_CLUSTER|M_EXT_PAGES)) {
case M_EXT|M_EXT_CLUSTER:
KASSERT(m->m_ext.ext_paddr != M_PADDR_INVALID);
paddr = m->m_ext.ext_paddr +
(m->m_data - m->m_ext.ext_buf);
size = m->m_len;
error = _bus_dmamap_load_busaddr(t, map,
_BUS_PHYS_TO_BUS(paddr), size);
break;
case M_EXT|M_EXT_PAGES:
KASSERTMSG(m->m_ext.ext_buf <= m->m_data,
"m=%p m_ext.ext_buf=%p m_ext.ext_size=%zu"
" m_data=%p",
m, m->m_ext.ext_buf, m->m_ext.ext_size, m->m_data);
KASSERTMSG((m->m_data <=
m->m_ext.ext_buf + m->m_ext.ext_size),
"m=%p m_ext.ext_buf=%p m_ext.ext_size=%zu"
" m_data=%p",
m, m->m_ext.ext_buf, m->m_ext.ext_size, m->m_data);
offset = (vaddr_t)m->m_data -
trunc_page((vaddr_t)m->m_ext.ext_buf);
remainbytes = m->m_len;
pgs = (const struct vm_page * const *)
m->m_ext.ext_pgs + (offset >> PAGE_SHIFT);
offset &= PAGE_MASK;
while (remainbytes > 0) {
const struct vm_page *pg;
bus_addr_t busaddr;
size = MIN(remainbytes, PAGE_SIZE - offset);
pg = *pgs++;
KASSERT(pg);
busaddr = _BUS_VM_PAGE_TO_BUS(pg) + offset;
error = _bus_dmamap_load_busaddr(t, map,
busaddr, size);
if (error)
break;
offset = 0;
remainbytes -= size;
}
break;
case 0:
paddr = m->m_paddr + M_BUFOFFSET(m) +
(m->m_data - M_BUFADDR(m));
size = m->m_len;
error = _bus_dmamap_load_busaddr(t, map,
_BUS_PHYS_TO_BUS(paddr), size);
break;
default:
error = _bus_dmamap_load_buffer(t, map, m->m_data,
m->m_len, vmspace_kernel(), flags);
}
}
if (error == 0) {
map->dm_mapsize = m0->m_pkthdr.len;
return 0;
}
map->dm_nsegs = 0;
if (cookie == NULL ||
(cookie->id_flags & X86_DMA_MIGHT_NEED_BOUNCE) == 0)
return error;
STAT_INCR(bounces);
if ((cookie->id_flags & X86_DMA_HAS_BOUNCE) == 0) {
error = _bus_dma_alloc_bouncebuf(t, map, m0->m_pkthdr.len,
flags);
if (error)
return (error);
}
cookie->id_origbuf = m0;
cookie->id_origbuflen = m0->m_pkthdr.len;
cookie->id_buftype = X86_DMA_BUFTYPE_MBUF;
error = bus_dmamap_load(t, map, cookie->id_bouncebuf,
m0->m_pkthdr.len, NULL, flags);
if (error)
return (error);
cookie->id_flags |= X86_DMA_IS_BOUNCING;
return (0);
}
static int
_bus_dmamap_load_uio(bus_dma_tag_t t, bus_dmamap_t map, struct uio *uio,
int flags)
{
int i, error;
bus_size_t minlen, resid;
struct vmspace *vm;
struct iovec *iov;
void *addr;
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERTMSG(map->dm_maxsegsz <= map->_dm_maxmaxsegsz,
"maxsegsz=0x%"PRIxBUSSIZE", maxmaxsegsz=0x%"PRIxBUSSIZE,
map->dm_maxsegsz, map->_dm_maxmaxsegsz);
resid = uio->uio_resid;
iov = uio->uio_iov;
vm = uio->uio_vmspace;
error = 0;
for (i = 0; i < uio->uio_iovcnt && resid != 0 && error == 0; i++) {
minlen = resid < iov[i].iov_len ? resid : iov[i].iov_len;
addr = (void *)iov[i].iov_base;
error = _bus_dmamap_load_buffer(t, map, addr, minlen,
vm, flags);
resid -= minlen;
}
if (error == 0) {
map->dm_mapsize = uio->uio_resid;
return 0;
}
map->dm_nsegs = 0;
if (cookie == NULL ||
(cookie->id_flags & X86_DMA_MIGHT_NEED_BOUNCE) == 0)
return error;
STAT_INCR(bounces);
if ((cookie->id_flags & X86_DMA_HAS_BOUNCE) == 0) {
error = _bus_dma_alloc_bouncebuf(t, map, uio->uio_resid,
flags);
if (error)
return (error);
}
cookie->id_origbuf = uio;
cookie->id_origbuflen = uio->uio_resid;
cookie->id_buftype = X86_DMA_BUFTYPE_UIO;
error = bus_dmamap_load(t, map, cookie->id_bouncebuf,
uio->uio_resid, NULL, flags);
if (error)
return (error);
cookie->id_flags |= X86_DMA_IS_BOUNCING;
return (0);
}
static int
_bus_dmamap_load_raw(bus_dma_tag_t t, bus_dmamap_t map,
bus_dma_segment_t *segs, int nsegs, bus_size_t size0, int flags)
{
bus_size_t size;
int i, error = 0;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERTMSG(map->dm_maxsegsz <= map->_dm_maxmaxsegsz,
"maxsegsz=0x%"PRIxBUSSIZE", maxmaxsegsz=0x%"PRIxBUSSIZE,
map->dm_maxsegsz, map->_dm_maxmaxsegsz);
if (size0 > map->_dm_size)
return EINVAL;
for (i = 0, size = size0; i < nsegs && size > 0; i++) {
bus_dma_segment_t *ds = &segs[i];
bus_size_t sgsize;
sgsize = MIN(ds->ds_len, size);
if (sgsize == 0)
continue;
error = _bus_dmamap_load_busaddr(t, map, ds->ds_addr, sgsize);
if (error != 0)
break;
size -= sgsize;
}
if (error != 0) {
map->dm_mapsize = 0;
map->dm_nsegs = 0;
return error;
}
map->dm_mapsize = size0;
return 0;
}
static void
_bus_dmamap_unload(bus_dma_tag_t t, bus_dmamap_t map)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
if (cookie != NULL) {
cookie->id_flags &= ~X86_DMA_IS_BOUNCING;
cookie->id_buftype = X86_DMA_BUFTYPE_INVALID;
}
map->dm_maxsegsz = map->_dm_maxmaxsegsz;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
}
static void
_bus_dmamap_sync(bus_dma_tag_t t, bus_dmamap_t map, bus_addr_t offset,
bus_size_t len, int ops)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
if ((ops & (BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE)) != 0 &&
(ops & (BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE)) != 0)
panic("%s: mix PRE and POST", __func__);
if ((ops & (BUS_DMASYNC_PREWRITE|BUS_DMASYNC_POSTREAD)) != 0) {
KASSERTMSG(offset < map->dm_mapsize,
"bad offset 0x%"PRIxBUSADDR" >= 0x%"PRIxBUSSIZE,
offset, map->dm_mapsize);
KASSERTMSG(len <= map->dm_mapsize - offset,
"bad length 0x%"PRIxBUSADDR" + 0x%"PRIxBUSSIZE
" > 0x%"PRIxBUSSIZE,
offset, len, map->dm_mapsize);
}
if (ops & BUS_DMASYNC_POSTREAD)
x86_lfence();
if (len == 0 || cookie == NULL ||
(cookie->id_flags & X86_DMA_IS_BOUNCING) == 0)
goto end;
switch (cookie->id_buftype) {
case X86_DMA_BUFTYPE_LINEAR:
if (ops & BUS_DMASYNC_PREWRITE) {
memcpy((char *)cookie->id_bouncebuf + offset,
(char *)cookie->id_origbuf + offset, len);
}
if (ops & BUS_DMASYNC_POSTREAD) {
memcpy((char *)cookie->id_origbuf + offset,
(char *)cookie->id_bouncebuf + offset, len);
}
break;
case X86_DMA_BUFTYPE_MBUF:
{
struct mbuf *m, *m0 = cookie->id_origbuf;
bus_size_t minlen, moff;
if (ops & BUS_DMASYNC_PREWRITE) {
m_copydata(m0, offset, len,
(char *)cookie->id_bouncebuf + offset);
}
if (ops & BUS_DMASYNC_POSTREAD) {
for (moff = offset, m = m0; m != NULL && len != 0;
m = m->m_next) {
if (moff >= m->m_len) {
moff -= m->m_len;
continue;
}
minlen = len < m->m_len - moff ?
len : m->m_len - moff;
memcpy(mtod(m, char *) + moff,
(char *)cookie->id_bouncebuf + offset,
minlen);
moff = 0;
len -= minlen;
offset += minlen;
}
}
break;
}
case X86_DMA_BUFTYPE_UIO:
{
struct uio *uio;
uio = (struct uio *)cookie->id_origbuf;
if (ops & BUS_DMASYNC_PREWRITE) {
_bus_dma_uiomove((char *)cookie->id_bouncebuf + offset,
uio, len, UIO_WRITE);
}
if (ops & BUS_DMASYNC_POSTREAD) {
_bus_dma_uiomove((char *)cookie->id_bouncebuf + offset,
uio, len, UIO_READ);
}
break;
}
case X86_DMA_BUFTYPE_RAW:
panic("%s: X86_DMA_BUFTYPE_RAW", __func__);
break;
case X86_DMA_BUFTYPE_INVALID:
panic("%s: X86_DMA_BUFTYPE_INVALID", __func__);
break;
default:
panic("%s: unknown buffer type %d", __func__,
cookie->id_buftype);
break;
}
end:
if (ops & (BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE))
x86_sfence();
}
static int
_bus_dmamem_alloc(bus_dma_tag_t t, bus_size_t size, bus_size_t alignment,
bus_size_t boundary, bus_dma_segment_t *segs, int nsegs, int *rsegs,
int flags)
{
bus_addr_t high;
if (t->_bounce_alloc_hi != 0 && _BUS_AVAIL_END > t->_bounce_alloc_hi - 1)
high = t->_bounce_alloc_hi - 1;
else
high = _BUS_AVAIL_END;
return (_BUS_DMAMEM_ALLOC_RANGE(t, size, alignment, boundary,
segs, nsegs, rsegs, flags, t->_bounce_alloc_lo, high));
}
static int
_bus_dma_alloc_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map,
bus_size_t size, int flags)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
int error = 0;
KASSERT(cookie != NULL);
cookie->id_bouncebuflen = round_page(size);
error = _bus_dmamem_alloc(t, cookie->id_bouncebuflen,
PAGE_SIZE, map->_dm_boundary, cookie->id_bouncesegs,
map->_dm_segcnt, &cookie->id_nbouncesegs, flags);
if (error) {
cookie->id_bouncebuflen = 0;
cookie->id_nbouncesegs = 0;
return error;
}
error = _bus_dmamem_map(t, cookie->id_bouncesegs,
cookie->id_nbouncesegs, cookie->id_bouncebuflen,
(void **)&cookie->id_bouncebuf, flags);
if (error) {
_bus_dmamem_free(t, cookie->id_bouncesegs,
cookie->id_nbouncesegs);
cookie->id_bouncebuflen = 0;
cookie->id_nbouncesegs = 0;
} else {
cookie->id_flags |= X86_DMA_HAS_BOUNCE;
STAT_INCR(nbouncebufs);
}
return (error);
}
static void
_bus_dma_free_bouncebuf(bus_dma_tag_t t, bus_dmamap_t map)
{
struct x86_bus_dma_cookie *cookie = map->_dm_cookie;
KASSERT(cookie != NULL);
STAT_DECR(nbouncebufs);
_bus_dmamem_unmap(t, cookie->id_bouncebuf, cookie->id_bouncebuflen);
_bus_dmamem_free(t, cookie->id_bouncesegs,
cookie->id_nbouncesegs);
cookie->id_bouncebuflen = 0;
cookie->id_nbouncesegs = 0;
cookie->id_flags &= ~X86_DMA_HAS_BOUNCE;
}
static int
_bus_dma_uiomove(void *buf, struct uio *uio, size_t n, int direction)
{
struct iovec *iov;
int error;
struct vmspace *vm;
char *cp;
size_t resid, cnt;
int i;
iov = uio->uio_iov;
vm = uio->uio_vmspace;
cp = buf;
resid = n;
for (i = 0; i < uio->uio_iovcnt && resid > 0; i++) {
iov = &uio->uio_iov[i];
if (iov->iov_len == 0)
continue;
cnt = MIN(resid, iov->iov_len);
if (!VMSPACE_IS_KERNEL_P(vm)) {
preempt_point();
}
if (direction == UIO_READ) {
error = copyout_vmspace(vm, cp, iov->iov_base, cnt);
} else {
error = copyin_vmspace(vm, iov->iov_base, cp, cnt);
}
if (error)
return (error);
cp += cnt;
resid -= cnt;
}
return (0);
}
static void
_bus_dmamem_free(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs)
{
struct vm_page *m;
bus_addr_t addr;
struct pglist mlist;
int curseg;
TAILQ_INIT(&mlist);
for (curseg = 0; curseg < nsegs; curseg++) {
for (addr = segs[curseg].ds_addr;
addr < (segs[curseg].ds_addr + segs[curseg].ds_len);
addr += PAGE_SIZE) {
m = _BUS_BUS_TO_VM_PAGE(addr);
TAILQ_INSERT_TAIL(&mlist, m, pageq.queue);
}
}
uvm_pglistfree(&mlist);
}
static int
_bus_dmamem_map(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs,
size_t size, void **kvap, int flags)
{
vaddr_t va;
bus_addr_t addr;
int curseg;
const uvm_flag_t kmflags =
(flags & BUS_DMA_NOWAIT) != 0 ? UVM_KMF_NOWAIT : 0;
u_int pmapflags = PMAP_WIRED | VM_PROT_READ | VM_PROT_WRITE;
size = round_page(size);
KASSERTMSG(((flags & (BUS_DMA_NOCACHE|BUS_DMA_PREFETCHABLE)) !=
(BUS_DMA_NOCACHE|BUS_DMA_PREFETCHABLE)),
"BUS_DMA_NOCACHE and BUS_DMA_PREFETCHABLE are mutually exclusive");
if (flags & BUS_DMA_NOCACHE)
pmapflags |= PMAP_NOCACHE;
if (flags & BUS_DMA_PREFETCHABLE)
pmapflags |= PMAP_WRITE_COMBINE;
va = uvm_km_alloc(kernel_map, size, 0, UVM_KMF_VAONLY | kmflags);
if (va == 0)
return ENOMEM;
*kvap = (void *)va;
for (curseg = 0; curseg < nsegs; curseg++) {
for (addr = segs[curseg].ds_addr;
addr < (segs[curseg].ds_addr + segs[curseg].ds_len);
addr += PAGE_SIZE, va += PAGE_SIZE, size -= PAGE_SIZE) {
if (size == 0)
panic("_bus_dmamem_map: size botch");
_BUS_PMAP_ENTER(pmap_kernel(), va, addr,
VM_PROT_READ | VM_PROT_WRITE,
pmapflags);
}
}
pmap_update(pmap_kernel());
return 0;
}
static void
_bus_dmamem_unmap(bus_dma_tag_t t, void *kva, size_t size)
{
pt_entry_t *pte, opte;
vaddr_t va, sva, eva;
KASSERTMSG(((uintptr_t)kva & PGOFSET) == 0, "kva=%p", kva);
size = round_page(size);
sva = (vaddr_t)kva;
eva = sva + size;
for (va = sva; va < eva; va += PAGE_SIZE) {
pte = kvtopte(va);
opte = *pte;
if ((opte & PTE_PCD) != 0)
pmap_pte_clearbits(pte, PTE_PCD);
}
pmap_remove(pmap_kernel(), (vaddr_t)kva, (vaddr_t)kva + size);
pmap_update(pmap_kernel());
uvm_km_free(kernel_map, (vaddr_t)kva, size, UVM_KMF_VAONLY);
}
static paddr_t
_bus_dmamem_mmap(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs,
off_t off, int prot, int flags)
{
int i;
for (i = 0; i < nsegs; i++) {
KASSERTMSG((off & PGOFSET) == 0, "off=0x%jx", (uintmax_t)off);
KASSERTMSG((segs[i].ds_addr & PGOFSET) == 0,
"segs[%u].ds_addr=%"PRIxBUSADDR, i, segs[i].ds_addr);
KASSERTMSG((segs[i].ds_len & PGOFSET) == 0,
"segs[%u].ds_len=%"PRIxBUSSIZE, i, segs[i].ds_len);
if (off >= segs[i].ds_len) {
off -= segs[i].ds_len;
continue;
}
return (x86_btop(_BUS_BUS_TO_PHYS(segs[i].ds_addr + off)));
}
return (-1);
}
static int
_bus_dmamap_load_buffer(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
bus_size_t buflen, struct vmspace *vm, int flags)
{
bus_size_t sgsize;
bus_addr_t curaddr;
vaddr_t vaddr = (vaddr_t)buf;
pmap_t pmap;
if (vm != NULL)
pmap = vm_map_pmap(&vm->vm_map);
else
pmap = pmap_kernel();
while (buflen > 0) {
int error;
curaddr = _BUS_VIRT_TO_BUS(pmap, vaddr);
sgsize = PAGE_SIZE - ((u_long)vaddr & PGOFSET);
if (buflen < sgsize)
sgsize = buflen;
if (map->_dm_bounce_thresh != 0 &&
curaddr + sgsize >= map->_dm_bounce_thresh)
return (EINVAL);
error = _bus_dmamap_load_busaddr(t, map, curaddr, sgsize);
if (error)
return error;
vaddr += sgsize;
buflen -= sgsize;
}
return (0);
}
static int
_bus_dmatag_subregion(bus_dma_tag_t tag, bus_addr_t min_addr,
bus_addr_t max_addr, bus_dma_tag_t *newtag, int flags)
{
if ((tag->_bounce_thresh != 0 && max_addr >= tag->_bounce_thresh - 1) &&
(tag->_bounce_alloc_hi != 0 && max_addr >= tag->_bounce_alloc_hi - 1) &&
(min_addr <= tag->_bounce_alloc_lo)) {
*newtag = tag;
if (tag->_tag_needs_free)
(tag->_tag_needs_free)++;
return 0;
}
if ((*newtag = malloc(sizeof(struct x86_bus_dma_tag), M_DMAMAP,
(flags & BUS_DMA_NOWAIT) ? M_NOWAIT : M_WAITOK)) == NULL)
return ENOMEM;
**newtag = *tag;
(*newtag)->_tag_needs_free = 1;
if (tag->_bounce_thresh == 0 || max_addr < tag->_bounce_thresh)
(*newtag)->_bounce_thresh = max_addr;
if (tag->_bounce_alloc_hi == 0 || max_addr < tag->_bounce_alloc_hi)
(*newtag)->_bounce_alloc_hi = max_addr;
if (min_addr > tag->_bounce_alloc_lo)
(*newtag)->_bounce_alloc_lo = min_addr;
return 0;
}
static void
_bus_dmatag_destroy(bus_dma_tag_t tag)
{
switch (tag->_tag_needs_free) {
case 0:
break;
case 1:
free(tag, M_DMAMAP);
break;
default:
(tag->_tag_needs_free)--;
}
}
void
bus_dmamap_sync(bus_dma_tag_t t, bus_dmamap_t p, bus_addr_t o, bus_size_t l,
int ops)
{
bus_dma_tag_t it;
kasan_dma_sync(p, o, l, ops);
kmsan_dma_sync(p, o, l, ops);
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_SYNC) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_SYNC) == 0)
continue;
(*it->bdt_ov->ov_dmamap_sync)(it->bdt_ctx, t, p, o,
l, ops);
return;
}
_bus_dmamap_sync(t, p, o, l, ops);
}
int
bus_dmamap_create(bus_dma_tag_t t, bus_size_t size, int nsegments,
bus_size_t maxsegsz, bus_size_t boundary, int flags,
bus_dmamap_t *dmamp)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_CREATE) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_CREATE) == 0)
continue;
return (*it->bdt_ov->ov_dmamap_create)(it->bdt_ctx, t, size,
nsegments, maxsegsz, boundary, flags, dmamp);
}
return _bus_dmamap_create(t, size, nsegments, maxsegsz,
boundary, flags, dmamp);
}
void
bus_dmamap_destroy(bus_dma_tag_t t, bus_dmamap_t dmam)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_DESTROY) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_DESTROY) == 0)
continue;
(*it->bdt_ov->ov_dmamap_destroy)(it->bdt_ctx, t, dmam);
return;
}
_bus_dmamap_destroy(t, dmam);
}
int
bus_dmamap_load(bus_dma_tag_t t, bus_dmamap_t dmam, void *buf,
bus_size_t buflen, struct proc *p, int flags)
{
bus_dma_tag_t it;
kasan_dma_load(dmam, buf, buflen, KASAN_DMA_LINEAR);
kmsan_dma_load(dmam, buf, buflen, KMSAN_DMA_LINEAR);
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_LOAD) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_LOAD) == 0)
continue;
return (*it->bdt_ov->ov_dmamap_load)(it->bdt_ctx, t, dmam,
buf, buflen, p, flags);
}
return _bus_dmamap_load(t, dmam, buf, buflen, p, flags);
}
int
bus_dmamap_load_mbuf(bus_dma_tag_t t, bus_dmamap_t dmam,
struct mbuf *chain, int flags)
{
bus_dma_tag_t it;
kasan_dma_load(dmam, chain, 0, KASAN_DMA_MBUF);
kmsan_dma_load(dmam, chain, 0, KMSAN_DMA_MBUF);
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_LOAD_MBUF) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_LOAD_MBUF) == 0)
continue;
return (*it->bdt_ov->ov_dmamap_load_mbuf)(it->bdt_ctx, t, dmam,
chain, flags);
}
return _bus_dmamap_load_mbuf(t, dmam, chain, flags);
}
int
bus_dmamap_load_uio(bus_dma_tag_t t, bus_dmamap_t dmam,
struct uio *uio, int flags)
{
bus_dma_tag_t it;
kasan_dma_load(dmam, uio, 0, KASAN_DMA_UIO);
kmsan_dma_load(dmam, uio, 0, KMSAN_DMA_UIO);
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_LOAD_UIO) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_LOAD_UIO) == 0)
continue;
return (*it->bdt_ov->ov_dmamap_load_uio)(it->bdt_ctx, t, dmam,
uio, flags);
}
return _bus_dmamap_load_uio(t, dmam, uio, flags);
}
int
bus_dmamap_load_raw(bus_dma_tag_t t, bus_dmamap_t dmam,
bus_dma_segment_t *segs, int nsegs,
bus_size_t size, int flags)
{
bus_dma_tag_t it;
kasan_dma_load(dmam, NULL, 0, KASAN_DMA_RAW);
kmsan_dma_load(dmam, NULL, 0, KMSAN_DMA_RAW);
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_LOAD_RAW) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_LOAD_RAW) == 0)
continue;
return (*it->bdt_ov->ov_dmamap_load_raw)(it->bdt_ctx, t, dmam,
segs, nsegs, size, flags);
}
return _bus_dmamap_load_raw(t, dmam, segs, nsegs, size, flags);
}
void
bus_dmamap_unload(bus_dma_tag_t t, bus_dmamap_t dmam)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMAP_OVERRIDE_UNLOAD) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMAP_OVERRIDE_UNLOAD) == 0)
continue;
(*it->bdt_ov->ov_dmamap_unload)(it->bdt_ctx, t, dmam);
return;
}
_bus_dmamap_unload(t, dmam);
}
int
bus_dmamem_alloc(bus_dma_tag_t t, bus_size_t size, bus_size_t alignment,
bus_size_t boundary, bus_dma_segment_t *segs, int nsegs,
int *rsegs, int flags)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMEM_OVERRIDE_ALLOC) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMEM_OVERRIDE_ALLOC) == 0)
continue;
return (*it->bdt_ov->ov_dmamem_alloc)(it->bdt_ctx, t, size,
alignment, boundary, segs, nsegs, rsegs, flags);
}
return _bus_dmamem_alloc(t, size, alignment, boundary, segs,
nsegs, rsegs, flags);
}
void
bus_dmamem_free(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMEM_OVERRIDE_FREE) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMEM_OVERRIDE_FREE) == 0)
continue;
(*it->bdt_ov->ov_dmamem_free)(it->bdt_ctx, t, segs, nsegs);
return;
}
_bus_dmamem_free(t, segs, nsegs);
}
int
bus_dmamem_map(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs,
size_t size, void **kvap, int flags)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMEM_OVERRIDE_MAP) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMEM_OVERRIDE_MAP) == 0)
continue;
return (*it->bdt_ov->ov_dmamem_map)(it->bdt_ctx, t,
segs, nsegs, size, kvap, flags);
}
return _bus_dmamem_map(t, segs, nsegs, size, kvap, flags);
}
void
bus_dmamem_unmap(bus_dma_tag_t t, void *kva, size_t size)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMEM_OVERRIDE_UNMAP) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMEM_OVERRIDE_UNMAP) == 0)
continue;
(*it->bdt_ov->ov_dmamem_unmap)(it->bdt_ctx, t, kva, size);
return;
}
_bus_dmamem_unmap(t, kva, size);
}
paddr_t
bus_dmamem_mmap(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs,
off_t off, int prot, int flags)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMAMEM_OVERRIDE_MMAP) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMAMEM_OVERRIDE_MMAP) == 0)
continue;
return (*it->bdt_ov->ov_dmamem_mmap)(it->bdt_ctx, t, segs,
nsegs, off, prot, flags);
}
return _bus_dmamem_mmap(t, segs, nsegs, off, prot, flags);
}
int
bus_dmatag_subregion(bus_dma_tag_t t, bus_addr_t min_addr,
bus_addr_t max_addr, bus_dma_tag_t *newtag, int flags)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMATAG_OVERRIDE_SUBREGION) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMATAG_OVERRIDE_SUBREGION) == 0)
continue;
return (*it->bdt_ov->ov_dmatag_subregion)(it->bdt_ctx, t,
min_addr, max_addr, newtag, flags);
}
return _bus_dmatag_subregion(t, min_addr, max_addr, newtag, flags);
}
void
bus_dmatag_destroy(bus_dma_tag_t t)
{
bus_dma_tag_t it;
if ((t->bdt_exists & BUS_DMATAG_OVERRIDE_DESTROY) == 0)
;
else for (it = t; it != NULL; it = it->bdt_super) {
if ((it->bdt_present & BUS_DMATAG_OVERRIDE_DESTROY) == 0)
continue;
(*it->bdt_ov->ov_dmatag_destroy)(it->bdt_ctx, t);
return;
}
_bus_dmatag_destroy(t);
}
static const void *
bit_to_function_pointer(const struct bus_dma_overrides *ov, uint64_t bit)
{
switch (bit) {
case BUS_DMAMAP_OVERRIDE_CREATE:
return ov->ov_dmamap_create;
case BUS_DMAMAP_OVERRIDE_DESTROY:
return ov->ov_dmamap_destroy;
case BUS_DMAMAP_OVERRIDE_LOAD:
return ov->ov_dmamap_load;
case BUS_DMAMAP_OVERRIDE_LOAD_MBUF:
return ov->ov_dmamap_load_mbuf;
case BUS_DMAMAP_OVERRIDE_LOAD_UIO:
return ov->ov_dmamap_load_uio;
case BUS_DMAMAP_OVERRIDE_LOAD_RAW:
return ov->ov_dmamap_load_raw;
case BUS_DMAMAP_OVERRIDE_UNLOAD:
return ov->ov_dmamap_unload;
case BUS_DMAMAP_OVERRIDE_SYNC:
return ov->ov_dmamap_sync;
case BUS_DMAMEM_OVERRIDE_ALLOC:
return ov->ov_dmamem_alloc;
case BUS_DMAMEM_OVERRIDE_FREE:
return ov->ov_dmamem_free;
case BUS_DMAMEM_OVERRIDE_MAP:
return ov->ov_dmamem_map;
case BUS_DMAMEM_OVERRIDE_UNMAP:
return ov->ov_dmamem_unmap;
case BUS_DMAMEM_OVERRIDE_MMAP:
return ov->ov_dmamem_mmap;
case BUS_DMATAG_OVERRIDE_SUBREGION:
return ov->ov_dmatag_subregion;
case BUS_DMATAG_OVERRIDE_DESTROY:
return ov->ov_dmatag_destroy;
default:
return NULL;
}
}
void
bus_dma_tag_destroy(bus_dma_tag_t bdt)
{
if (bdt->bdt_super != NULL)
bus_dmatag_destroy(bdt->bdt_super);
kmem_free(bdt, sizeof(struct x86_bus_dma_tag));
}
int
bus_dma_tag_create(bus_dma_tag_t obdt, const uint64_t present,
const struct bus_dma_overrides *ov, void *ctx, bus_dma_tag_t *bdtp)
{
uint64_t bit, bits, nbits;
bus_dma_tag_t bdt;
const void *fp;
if (ov == NULL || present == 0)
return EINVAL;
bdt = kmem_alloc(sizeof(struct x86_bus_dma_tag), KM_SLEEP);
*bdt = *obdt;
bdt->_tag_needs_free = 0;
bdt->bdt_super = obdt;
for (bits = present; bits != 0; bits = nbits) {
nbits = bits & (bits - 1);
bit = nbits ^ bits;
if ((fp = bit_to_function_pointer(ov, bit)) == NULL) {
#ifdef DEBUG
printf("%s: missing bit %" PRIx64 "\n", __func__, bit);
#endif
goto einval;
}
}
bdt->bdt_ov = ov;
bdt->bdt_exists = obdt->bdt_exists | present;
bdt->bdt_present = present;
bdt->bdt_ctx = ctx;
*bdtp = bdt;
if (obdt->_tag_needs_free)
obdt->_tag_needs_free++;
return 0;
einval:
kmem_free(bdt, sizeof(struct x86_bus_dma_tag));
return EINVAL;
}