Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102723383
D42933.id132518.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D42933.id132518.diff
View Options
diff --git a/sys/kern/subr_memdesc.c b/sys/kern/subr_memdesc.c
--- a/sys/kern/subr_memdesc.c
+++ b/sys/kern/subr_memdesc.c
@@ -33,9 +33,14 @@
#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>
+/*
+ * memdesc_copyback copies data from a source buffer into a buffer
+ * described by a memory descriptor.
+ */
static void
phys_copyback(vm_paddr_t pa, int off, int size, const void *src)
{
@@ -180,6 +185,10 @@
}
}
+/*
+ * memdesc_copydata copies data from a buffer described by a memory
+ * descriptor into a destination buffer.
+ */
static void
phys_copydata(vm_paddr_t pa, int off, int size, void *dst)
{
@@ -323,3 +332,468 @@
__assert_unreachable();
}
}
+
+/*
+ * memdesc_alloc_ext_mbufs allocates a chain of external mbufs backed
+ * by the storage of a memory descriptor's data buffer.
+ */
+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;
+
+ /* Can always append to an empty mbuf. */
+ if (m->m_epg_npgs == 0)
+ return (true);
+
+ /* Can't append to a full mbuf. */
+ if (m->m_epg_npgs == MBUF_PEXT_MAX_PGS)
+ return (false);
+
+ /* Can't append a non-page-aligned address to a non-empty mbuf. */
+ if ((pa & PAGE_MASK) != 0)
+ return (false);
+
+ /* Can't append if the last page is not a full page. */
+ 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);
+}
+
+/*
+ * Returns amount of data added to an M_EXTPG mbuf.
+ */
+static size_t
+append_paddr_range(struct mbuf *m, vm_paddr_t pa, size_t len)
+{
+ size_t appended;
+
+ appended = 0;
+
+ /* Append the first page. */
+ 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"));
+
+ /* 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;
+ }
+
+ /* Final partial page. */
+ 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;
+
+ /*
+ * Trim any partial page at the end, but not if it's
+ * the only page.
+ */
+ 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;
+
+ /*
+ * If truncation is enabled, avoid sending a final
+ * partial page, but only if there is more data
+ * available in the current segment. Also, at least
+ * some data must be sent, so only drop the final page
+ * for this segment if the segment spans multiple
+ * pages or some other data is already queued.
+ */
+ else if (can_truncate) {
+ vm_paddr_t end;
+
+ end = trunc_page(pa + len);
+ if (end <= pa && totlen != 0) {
+ /*
+ * This last segment is only a partial
+ * page.
+ */
+ 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;
+
+ /*
+ * Trim any partial page at the end, but not if it's
+ * the only page.
+ */
+ 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);
+
+ /* First page. */
+ 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;
+
+ /* Full pages. */
+ 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;
+ }
+
+ /* Last partial page. */
+ 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);
+}
+
+/*
+ * Somewhat similar to m_copym but optionally avoids a partial mbuf at
+ * the end.
+ */
+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;
+ }
+
+ /* Always return at least one mbuf. */
+ 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 truncation is enabled, don't send any partial
+ * mbufs besides the first one.
+ */
+ 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);
+}
diff --git a/sys/sys/memdesc.h b/sys/sys/memdesc.h
--- a/sys/sys/memdesc.h
+++ b/sys/sys/memdesc.h
@@ -163,4 +163,40 @@
const void *src);
void memdesc_copydata(struct memdesc *mem, int off, int size, void *dst);
+/*
+ * This routine constructs a chain of M_EXT mbufs backed by a data
+ * buffer described by a memory descriptor. Some buffers may require
+ * multiple mbufs. For memory descriptors using unmapped storage
+ * (e.g. memdesc_vmpages), M_EXTPG mbufs are used.
+ *
+ * Since memory descriptors are not an actual buffer, just a
+ * description of the buffer, the caller is required to supply a
+ * couple of helper routines to manage allocation of the raw mbufs and
+ * associate them with a reference to the underlying buffer.
+ *
+ * The memdesc_alloc_ext_mbuf_t callback is passed the callback
+ * argument as its first argument, the how flag as its second
+ * argument, and the pointer and length of a KVA buffer. This
+ * callback should allocate an mbuf for the KVA buffer, either by
+ * making a copy of the data or using m_extaddref().
+ *
+ * The memdesc_alloc_extpg_mbuf_t callback is passed the callback
+ * argument as its first argument and the how flag as its second
+ * argument. It should return an empty mbuf allocated by
+ * mb_alloc_ext_pgs.
+ *
+ * If either of the callbacks returns NULL, any partially allocated
+ * chain is freed and this routine returns NULL.
+ *
+ * If can_truncate is true, then this function might return a short
+ * chain to avoid gratuitously splitting up a page.
+ */
+typedef struct mbuf *memdesc_alloc_ext_mbuf_t(void *, int, void *, size_t);
+typedef struct mbuf *memdesc_alloc_extpg_mbuf_t(void *, int);
+
+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);
+
#endif /* _SYS_MEMDESC_H_ */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 17, 9:18 AM (16 h, 50 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14673715
Default Alt Text
D42933.id132518.diff (13 KB)
Attached To
Mode
D42933: memdesc: Helper function to construct mbuf chain backed by memdesc buffer
Attached
Detach File
Event Timeline
Log In to Comment