#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/memdesc.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_page.h>
#include <vm/vm_param.h>
#include <machine/bus.h>
static void
phys_copyback(vm_paddr_t pa, int off, int size, const void *src)
{
const char *cp;
u_int page_off;
int todo;
void *p;
KASSERT(PMAP_HAS_DMAP, ("direct-map required"));
cp = src;
pa += off;
page_off = pa & PAGE_MASK;
while (size > 0) {
todo = min(PAGE_SIZE - page_off, size);
p = PHYS_TO_DMAP(pa);
memcpy(p, cp, todo);
size -= todo;
cp += todo;
pa += todo;
page_off = 0;
}
}
static void
vlist_copyback(struct bus_dma_segment *vlist, int sglist_cnt, int off,
int size, const void *src)
{
const char *p;
int todo;
while (vlist->ds_len <= off) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
off -= vlist->ds_len;
vlist++;
sglist_cnt--;
}
p = src;
while (size > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
todo = size;
if (todo > vlist->ds_len - off)
todo = vlist->ds_len - off;
memcpy((char *)(uintptr_t)vlist->ds_addr + off, p, todo);
off = 0;
vlist++;
sglist_cnt--;
size -= todo;
p += todo;
}
}
static void
plist_copyback(struct bus_dma_segment *plist, int sglist_cnt, int off,
int size, const void *src)
{
const char *p;
int todo;
while (plist->ds_len <= off) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
off -= plist->ds_len;
plist++;
sglist_cnt--;
}
p = src;
while (size > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
todo = size;
if (todo > plist->ds_len - off)
todo = plist->ds_len - off;
phys_copyback(plist->ds_addr, off, todo, p);
off = 0;
plist++;
sglist_cnt--;
size -= todo;
p += todo;
}
}
static void
vmpages_copyback(vm_page_t *m, int off, int size, const void *src)
{
struct iovec iov[1];
struct uio uio;
int error __diagused;
iov[0].iov_base = __DECONST(void *, src);
iov[0].iov_len = size;
uio.uio_iov = iov;
uio.uio_iovcnt = 1;
uio.uio_offset = 0;
uio.uio_resid = size;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_rw = UIO_WRITE;
error = uiomove_fromphys(m, off, size, &uio);
KASSERT(error == 0 && uio.uio_resid == 0, ("copy failed"));
}
void
memdesc_copyback(struct memdesc *mem, int off, int size, const void *src)
{
KASSERT(off >= 0, ("%s: invalid offset %d", __func__, off));
KASSERT(size >= 0, ("%s: invalid size %d", __func__, off));
switch (mem->md_type) {
case MEMDESC_VADDR:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
memcpy((char *)mem->u.md_vaddr + off, src, size);
break;
case MEMDESC_PADDR:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
phys_copyback(mem->u.md_paddr, off, size, src);
break;
case MEMDESC_VLIST:
vlist_copyback(mem->u.md_list, mem->md_nseg, off, size, src);
break;
case MEMDESC_PLIST:
plist_copyback(mem->u.md_list, mem->md_nseg, off, size, src);
break;
case MEMDESC_UIO:
panic("Use uiomove instead");
break;
case MEMDESC_MBUF:
m_copyback(mem->u.md_mbuf, off, size, src);
break;
case MEMDESC_VMPAGES:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
vmpages_copyback(mem->u.md_ma, mem->md_offset + off, size,
src);
break;
default:
__assert_unreachable();
}
}
static void
phys_copydata(vm_paddr_t pa, int off, int size, void *dst)
{
char *cp;
u_int page_off;
int todo;
const void *p;
KASSERT(PMAP_HAS_DMAP, ("direct-map required"));
cp = dst;
pa += off;
page_off = pa & PAGE_MASK;
while (size > 0) {
todo = min(PAGE_SIZE - page_off, size);
p = PHYS_TO_DMAP(pa);
memcpy(cp, p, todo);
size -= todo;
cp += todo;
pa += todo;
page_off = 0;
}
}
static void
vlist_copydata(struct bus_dma_segment *vlist, int sglist_cnt, int off,
int size, void *dst)
{
char *p;
int todo;
while (vlist->ds_len <= off) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
off -= vlist->ds_len;
vlist++;
sglist_cnt--;
}
p = dst;
while (size > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
todo = size;
if (todo > vlist->ds_len - off)
todo = vlist->ds_len - off;
memcpy(p, (char *)(uintptr_t)vlist->ds_addr + off, todo);
off = 0;
vlist++;
sglist_cnt--;
size -= todo;
p += todo;
}
}
static void
plist_copydata(struct bus_dma_segment *plist, int sglist_cnt, int off,
int size, void *dst)
{
char *p;
int todo;
while (plist->ds_len <= off) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
off -= plist->ds_len;
plist++;
sglist_cnt--;
}
p = dst;
while (size > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
todo = size;
if (todo > plist->ds_len - off)
todo = plist->ds_len - off;
phys_copydata(plist->ds_addr, off, todo, p);
off = 0;
plist++;
sglist_cnt--;
size -= todo;
p += todo;
}
}
static void
vmpages_copydata(vm_page_t *m, int off, int size, void *dst)
{
struct iovec iov[1];
struct uio uio;
int error __diagused;
iov[0].iov_base = dst;
iov[0].iov_len = size;
uio.uio_iov = iov;
uio.uio_iovcnt = 1;
uio.uio_offset = 0;
uio.uio_resid = size;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_rw = UIO_READ;
error = uiomove_fromphys(m, off, size, &uio);
KASSERT(error == 0 && uio.uio_resid == 0, ("copy failed"));
}
void
memdesc_copydata(struct memdesc *mem, int off, int size, void *dst)
{
KASSERT(off >= 0, ("%s: invalid offset %d", __func__, off));
KASSERT(size >= 0, ("%s: invalid size %d", __func__, off));
switch (mem->md_type) {
case MEMDESC_VADDR:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
memcpy(dst, (const char *)mem->u.md_vaddr + off, size);
break;
case MEMDESC_PADDR:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
phys_copydata(mem->u.md_paddr, off, size, dst);
break;
case MEMDESC_VLIST:
vlist_copydata(mem->u.md_list, mem->md_nseg, off, size, dst);
break;
case MEMDESC_PLIST:
plist_copydata(mem->u.md_list, mem->md_nseg, off, size, dst);
break;
case MEMDESC_UIO:
panic("Use uiomove instead");
break;
case MEMDESC_MBUF:
m_copydata(mem->u.md_mbuf, off, size, dst);
break;
case MEMDESC_VMPAGES:
KASSERT(off + size <= mem->md_len, ("copy out of bounds"));
vmpages_copydata(mem->u.md_ma, mem->md_offset + off, size,
dst);
break;
default:
__assert_unreachable();
}
}
static struct mbuf *
vaddr_ext_mbuf(memdesc_alloc_ext_mbuf_t *ext_alloc, void *cb_arg, int how,
void *buf, size_t len, size_t *actual_len)
{
*actual_len = len;
return (ext_alloc(cb_arg, how, buf, len));
}
static bool
can_append_paddr(struct mbuf *m, vm_paddr_t pa)
{
u_int last_len;
if (m->m_epg_npgs == 0)
return (true);
if (m->m_epg_npgs == MBUF_PEXT_MAX_PGS)
return (false);
if ((pa & PAGE_MASK) != 0)
return (false);
last_len = m->m_epg_last_len;
if (m->m_epg_npgs == 1)
last_len += m->m_epg_1st_off;
return (last_len == PAGE_SIZE);
}
static size_t
append_paddr_range(struct mbuf *m, vm_paddr_t pa, size_t len)
{
size_t appended;
appended = 0;
if (m->m_epg_npgs == 0) {
m->m_epg_pa[0] = trunc_page(pa);
m->m_epg_npgs = 1;
m->m_epg_1st_off = pa & PAGE_MASK;
m->m_epg_last_len = PAGE_SIZE - m->m_epg_1st_off;
if (m->m_epg_last_len > len)
m->m_epg_last_len = len;
m->m_len = m->m_epg_last_len;
len -= m->m_epg_last_len;
pa += m->m_epg_last_len;
appended += m->m_epg_last_len;
}
KASSERT(len == 0 || (pa & PAGE_MASK) == 0,
("PA not aligned before full pages"));
while (len >= PAGE_SIZE && m->m_epg_npgs < MBUF_PEXT_MAX_PGS) {
m->m_epg_pa[m->m_epg_npgs] = pa;
m->m_epg_npgs++;
m->m_epg_last_len = PAGE_SIZE;
m->m_len += PAGE_SIZE;
pa += PAGE_SIZE;
len -= PAGE_SIZE;
appended += PAGE_SIZE;
}
if (len > 0 && m->m_epg_npgs < MBUF_PEXT_MAX_PGS) {
KASSERT(len < PAGE_SIZE, ("final page is full page"));
m->m_epg_pa[m->m_epg_npgs] = pa;
m->m_epg_npgs++;
m->m_epg_last_len = len;
m->m_len += len;
appended += len;
}
return (appended);
}
static struct mbuf *
paddr_ext_mbuf(memdesc_alloc_extpg_mbuf_t *extpg_alloc, void *cb_arg, int how,
vm_paddr_t pa, size_t len, size_t *actual_len, bool can_truncate)
{
struct mbuf *m, *tail;
size_t appended;
if (can_truncate) {
vm_paddr_t end;
end = trunc_page(pa + len);
if (end > pa)
len = end - pa;
}
*actual_len = len;
m = tail = extpg_alloc(cb_arg, how);
if (m == NULL)
return (NULL);
while (len > 0) {
if (!can_append_paddr(tail, pa)) {
MBUF_EXT_PGS_ASSERT_SANITY(tail);
tail->m_next = extpg_alloc(cb_arg, how);
if (tail->m_next == NULL)
goto error;
tail = tail->m_next;
}
appended = append_paddr_range(tail, pa, len);
KASSERT(appended > 0, ("did not append anything"));
KASSERT(appended <= len, ("appended too much"));
pa += appended;
len -= appended;
}
MBUF_EXT_PGS_ASSERT_SANITY(tail);
return (m);
error:
m_freem(m);
return (NULL);
}
static struct mbuf *
vlist_ext_mbuf(memdesc_alloc_ext_mbuf_t *ext_alloc, void *cb_arg, int how,
struct bus_dma_segment *vlist, u_int sglist_cnt, size_t offset,
size_t len, size_t *actual_len)
{
struct mbuf *m, *n, *tail;
size_t todo;
*actual_len = len;
while (vlist->ds_len <= offset) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
offset -= vlist->ds_len;
vlist++;
sglist_cnt--;
}
m = tail = NULL;
while (len > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
todo = len;
if (todo > vlist->ds_len - offset)
todo = vlist->ds_len - offset;
n = ext_alloc(cb_arg, how, (char *)(uintptr_t)vlist->ds_addr +
offset, todo);
if (n == NULL)
goto error;
if (m == NULL) {
m = n;
tail = m;
} else {
tail->m_next = n;
tail = n;
}
offset = 0;
vlist++;
sglist_cnt--;
len -= todo;
}
return (m);
error:
m_freem(m);
return (NULL);
}
static struct mbuf *
plist_ext_mbuf(memdesc_alloc_extpg_mbuf_t *extpg_alloc, void *cb_arg, int how,
struct bus_dma_segment *plist, u_int sglist_cnt, size_t offset, size_t len,
size_t *actual_len, bool can_truncate)
{
vm_paddr_t pa;
struct mbuf *m, *tail;
size_t appended, totlen, todo;
while (plist->ds_len <= offset) {
KASSERT(sglist_cnt > 1, ("out of sglist entries"));
offset -= plist->ds_len;
plist++;
sglist_cnt--;
}
totlen = 0;
m = tail = extpg_alloc(cb_arg, how);
if (m == NULL)
return (NULL);
while (len > 0) {
KASSERT(sglist_cnt >= 1, ("out of sglist entries"));
pa = plist->ds_addr + offset;
todo = len;
if (todo > plist->ds_len - offset)
todo = plist->ds_len - offset;
else if (can_truncate) {
vm_paddr_t end;
end = trunc_page(pa + len);
if (end <= pa && totlen != 0) {
len = 0;
break;
}
todo = end - pa;
}
offset = 0;
len -= todo;
totlen += todo;
while (todo > 0) {
if (!can_append_paddr(tail, pa)) {
MBUF_EXT_PGS_ASSERT_SANITY(tail);
tail->m_next = extpg_alloc(cb_arg, how);
if (tail->m_next == NULL)
goto error;
tail = tail->m_next;
}
appended = append_paddr_range(tail, pa, todo);
KASSERT(appended > 0, ("did not append anything"));
pa += appended;
todo -= appended;
}
}
MBUF_EXT_PGS_ASSERT_SANITY(tail);
*actual_len = totlen;
return (m);
error:
m_freem(m);
return (NULL);
}
static struct mbuf *
vmpages_ext_mbuf(memdesc_alloc_extpg_mbuf_t *extpg_alloc, void *cb_arg, int how,
vm_page_t *ma, size_t offset, size_t len, size_t *actual_len,
bool can_truncate)
{
struct mbuf *m, *tail;
while (offset >= PAGE_SIZE) {
ma++;
offset -= PAGE_SIZE;
}
if (can_truncate) {
size_t end;
end = trunc_page(offset + len);
if (end > offset)
len = end - offset;
}
*actual_len = len;
m = tail = extpg_alloc(cb_arg, how);
if (m == NULL)
return (NULL);
m->m_epg_pa[0] = VM_PAGE_TO_PHYS(*ma);
ma++;
m->m_epg_npgs = 1;
m->m_epg_1st_off = offset;
m->m_epg_last_len = PAGE_SIZE - offset;
if (m->m_epg_last_len > len)
m->m_epg_last_len = len;
m->m_len = m->m_epg_last_len;
len -= m->m_epg_last_len;
while (len >= PAGE_SIZE) {
if (tail->m_epg_npgs == MBUF_PEXT_MAX_PGS) {
MBUF_EXT_PGS_ASSERT_SANITY(tail);
tail->m_next = extpg_alloc(cb_arg, how);
if (tail->m_next == NULL)
goto error;
tail = tail->m_next;
}
tail->m_epg_pa[tail->m_epg_npgs] = VM_PAGE_TO_PHYS(*ma);
ma++;
tail->m_epg_npgs++;
tail->m_epg_last_len = PAGE_SIZE;
tail->m_len += PAGE_SIZE;
len -= PAGE_SIZE;
}
if (len > 0) {
if (tail->m_epg_npgs == MBUF_PEXT_MAX_PGS) {
MBUF_EXT_PGS_ASSERT_SANITY(tail);
tail->m_next = extpg_alloc(cb_arg, how);
if (tail->m_next == NULL)
goto error;
tail = tail->m_next;
}
tail->m_epg_pa[tail->m_epg_npgs] = VM_PAGE_TO_PHYS(*ma);
ma++;
tail->m_epg_npgs++;
tail->m_epg_last_len = len;
tail->m_len += len;
}
MBUF_EXT_PGS_ASSERT_SANITY(tail);
return (m);
error:
m_freem(m);
return (NULL);
}
static struct mbuf *
mbuf_subchain(struct mbuf *m0, size_t offset, size_t len,
size_t *actual_len, bool can_truncate, int how)
{
struct mbuf *m, *tail;
size_t totlen;
while (offset >= m0->m_len) {
offset -= m0->m_len;
m0 = m0->m_next;
}
totlen = m0->m_len - offset;
if (totlen > len)
totlen = len;
m = m_get(how, MT_DATA);
if (m == NULL)
return (NULL);
m->m_len = totlen;
if (m0->m_flags & (M_EXT | M_EXTPG)) {
m->m_data = m0->m_data + offset;
mb_dupcl(m, m0);
} else
memcpy(mtod(m, void *), mtodo(m0, offset), m->m_len);
tail = m;
m0 = m0->m_next;
len -= totlen;
while (len > 0) {
if (can_truncate && m0->m_len > len)
break;
tail->m_next = m_get(how, MT_DATA);
if (tail->m_next == NULL)
goto error;
tail = tail->m_next;
tail->m_len = m0->m_len;
if (m0->m_flags & (M_EXT | M_EXTPG)) {
tail->m_data = m0->m_data;
mb_dupcl(tail, m0);
} else
memcpy(mtod(tail, void *), mtod(m0, void *),
tail->m_len);
totlen += tail->m_len;
m0 = m0->m_next;
len -= tail->m_len;
}
*actual_len = totlen;
return (m);
error:
m_freem(m);
return (NULL);
}
struct mbuf *
memdesc_alloc_ext_mbufs(struct memdesc *mem,
memdesc_alloc_ext_mbuf_t *ext_alloc,
memdesc_alloc_extpg_mbuf_t *extpg_alloc, void *cb_arg, int how,
size_t offset, size_t len, size_t *actual_len, bool can_truncate)
{
struct mbuf *m;
size_t done;
switch (mem->md_type) {
case MEMDESC_VADDR:
m = vaddr_ext_mbuf(ext_alloc, cb_arg, how,
(char *)mem->u.md_vaddr + offset, len, &done);
break;
case MEMDESC_PADDR:
m = paddr_ext_mbuf(extpg_alloc, cb_arg, how, mem->u.md_paddr +
offset, len, &done, can_truncate);
break;
case MEMDESC_VLIST:
m = vlist_ext_mbuf(ext_alloc, cb_arg, how, mem->u.md_list,
mem->md_nseg, offset, len, &done);
break;
case MEMDESC_PLIST:
m = plist_ext_mbuf(extpg_alloc, cb_arg, how, mem->u.md_list,
mem->md_nseg, offset, len, &done, can_truncate);
break;
case MEMDESC_UIO:
panic("uio not supported");
case MEMDESC_MBUF:
m = mbuf_subchain(mem->u.md_mbuf, offset, len, &done,
can_truncate, how);
break;
case MEMDESC_VMPAGES:
m = vmpages_ext_mbuf(extpg_alloc, cb_arg, how, mem->u.md_ma,
mem->md_offset + offset, len, &done, can_truncate);
break;
default:
__assert_unreachable();
}
if (m == NULL)
return (NULL);
if (can_truncate) {
KASSERT(done <= len, ("chain too long"));
} else {
KASSERT(done == len, ("short chain with no limit"));
}
KASSERT(m_length(m, NULL) == done, ("length mismatch"));
if (actual_len != NULL)
*actual_len = done;
return (m);
}