git: 1db9d7b7751c - stable/13 - cxgbei: Support unmapped I/O requests.

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Wed, 11 May 2022 21:04:40 UTC
The branch stable/13 has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=1db9d7b7751cb3e4550f1f7ec8e831eae72d54f2

commit 1db9d7b7751cb3e4550f1f7ec8e831eae72d54f2
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2022-03-10 23:50:52 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2022-05-11 20:52:48 +0000

    cxgbei: Support unmapped I/O requests.
    
    - Add icl_pdu_append_bio and icl_pdu_get_bio methods.
    
    - Add new page pod routines for allocating and writing page pods for
      unmapped bio requests.  Use these new routines for setting up DDP
      for iSCSI tasks with a SCSI I/O CCB which uses CAM_DATA_BIO.
    
    - When ICL_NOCOPY is used to append data from an unmapped I/O request
      to a PDU, construct unmapped mbufs from the relevant pages backing
      the struct bio.  This also requires changes in the t4_push_pdus path
      to support unmapped mbufs.
    
    Sponsored by:   Chelsio Communications
    Differential Revision:  https://reviews.freebsd.org/D34383
    
    (cherry picked from commit 2beaefe88443f0644d65b3ae0ac2f1ccff51e748)
---
 sys/dev/cxgbe/cxgbei/icl_cxgbei.c | 344 +++++++++++++++++++++++++++++++-------
 sys/dev/cxgbe/tom/t4_cpl_io.c     |  17 +-
 sys/dev/cxgbe/tom/t4_ddp.c        | 126 ++++++++++++--
 sys/dev/cxgbe/tom/t4_tom.h        |   5 +
 4 files changed, 416 insertions(+), 76 deletions(-)

diff --git a/sys/dev/cxgbe/cxgbei/icl_cxgbei.c b/sys/dev/cxgbe/cxgbei/icl_cxgbei.c
index 0d4f9b07ae2f..7d58e11b7835 100644
--- a/sys/dev/cxgbe/cxgbei/icl_cxgbei.c
+++ b/sys/dev/cxgbe/cxgbei/icl_cxgbei.c
@@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
 
 #ifdef TCP_OFFLOAD
 #include <sys/param.h>
+#include <sys/bio.h>
 #include <sys/capsicum.h>
 #include <sys/condvar.h>
 #include <sys/conf.h>
@@ -61,6 +62,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/uio.h>
 #include <machine/bus.h>
 #include <vm/vm.h>
+#include <vm/vm_page.h>
 #include <vm/pmap.h>
 #include <netinet/in.h>
 #include <netinet/in_pcb.h>
@@ -132,7 +134,9 @@ static volatile u_int icl_cxgbei_ncons;
 static icl_conn_new_pdu_t	icl_cxgbei_conn_new_pdu;
 static icl_conn_pdu_data_segment_length_t
 				    icl_cxgbei_conn_pdu_data_segment_length;
+static icl_conn_pdu_append_bio_t	icl_cxgbei_conn_pdu_append_bio;
 static icl_conn_pdu_append_data_t	icl_cxgbei_conn_pdu_append_data;
+static icl_conn_pdu_get_bio_t	icl_cxgbei_conn_pdu_get_bio;
 static icl_conn_pdu_get_data_t	icl_cxgbei_conn_pdu_get_data;
 static icl_conn_pdu_queue_t	icl_cxgbei_conn_pdu_queue;
 static icl_conn_pdu_queue_cb_t	icl_cxgbei_conn_pdu_queue_cb;
@@ -149,7 +153,9 @@ static kobj_method_t icl_cxgbei_methods[] = {
 	KOBJMETHOD(icl_conn_pdu_free, icl_cxgbei_conn_pdu_free),
 	KOBJMETHOD(icl_conn_pdu_data_segment_length,
 	    icl_cxgbei_conn_pdu_data_segment_length),
+	KOBJMETHOD(icl_conn_pdu_append_bio, icl_cxgbei_conn_pdu_append_bio),
 	KOBJMETHOD(icl_conn_pdu_append_data, icl_cxgbei_conn_pdu_append_data),
+	KOBJMETHOD(icl_conn_pdu_get_bio, icl_cxgbei_conn_pdu_get_bio),
 	KOBJMETHOD(icl_conn_pdu_get_data, icl_cxgbei_conn_pdu_get_data),
 	KOBJMETHOD(icl_conn_pdu_queue, icl_cxgbei_conn_pdu_queue),
 	KOBJMETHOD(icl_conn_pdu_queue_cb, icl_cxgbei_conn_pdu_queue_cb),
@@ -552,6 +558,189 @@ out:
 	kthread_exit();
 }
 
+static void
+cxgbei_free_mext_pg(struct mbuf *m)
+{
+	struct icl_cxgbei_pdu *icp;
+
+	M_ASSERTEXTPG(m);
+
+	/*
+	 * Nothing to do for the pages; they are owned by the PDU /
+	 * I/O request.
+	 */
+
+	/* Drop reference on the PDU. */
+	icp = m->m_ext.ext_arg1;
+	if (atomic_fetchadd_int(&icp->ref_cnt, -1) == 1)
+		icl_cxgbei_pdu_call_cb(&icp->ip);
+}
+
+static struct mbuf *
+cxgbei_getm(size_t len, int flags)
+{
+	struct mbuf *m, *m0, *m_tail;
+
+	m_tail = m0 = NULL;
+
+	/* Allocate as jumbo mbufs of size MJUM16BYTES. */
+	while (len >= MJUM16BYTES) {
+		m = m_getjcl(M_NOWAIT, MT_DATA, 0, MJUM16BYTES);
+		if (__predict_false(m == NULL)) {
+			if ((flags & M_WAITOK) != 0) {
+				/* Fall back to non-jumbo mbufs. */
+				break;
+			}
+			return (NULL);
+		}
+		if (m0 == NULL) {
+			m0 = m_tail = m;
+		} else {
+			m_tail->m_next = m;
+			m_tail = m;
+		}
+		len -= MJUM16BYTES;
+	}
+
+	/* Allocate mbuf chain for the remaining data. */
+	if (len != 0) {
+		m = m_getm2(NULL, len, flags, MT_DATA, 0);
+		if (__predict_false(m == NULL)) {
+			m_freem(m0);
+			return (NULL);
+		}
+		if (m0 == NULL)
+			m0 = m;
+		else
+			m_tail->m_next = m;
+	}
+
+	return (m0);
+}
+
+int
+icl_cxgbei_conn_pdu_append_bio(struct icl_conn *ic, struct icl_pdu *ip,
+    struct bio *bp, size_t offset, size_t len, int flags)
+{
+	struct icl_cxgbei_pdu *icp = ip_to_icp(ip);
+	struct mbuf *m, *m_tail;
+	vm_offset_t vaddr;
+	size_t page_offset, todo, mtodo;
+	boolean_t mapped;
+	int i;
+
+	MPASS(icp->icp_signature == CXGBEI_PDU_SIGNATURE);
+	MPASS(ic == ip->ip_conn);
+	KASSERT(len > 0, ("%s: len is %jd", __func__, (intmax_t)len));
+
+	m_tail = ip->ip_data_mbuf;
+	if (m_tail != NULL)
+		for (; m_tail->m_next != NULL; m_tail = m_tail->m_next)
+			;
+
+	MPASS(bp->bio_flags & BIO_UNMAPPED);
+	if (offset < PAGE_SIZE - bp->bio_ma_offset) {
+		page_offset = bp->bio_ma_offset + offset;
+		i = 0;
+	} else {
+		offset -= PAGE_SIZE - bp->bio_ma_offset;
+		for (i = 1; offset >= PAGE_SIZE; i++)
+			offset -= PAGE_SIZE;
+		page_offset = offset;
+	}
+
+	if (flags & ICL_NOCOPY) {
+		m = NULL;
+		while (len > 0) {
+			if (m == NULL) {
+				m = mb_alloc_ext_pgs(flags & ~ICL_NOCOPY,
+				    cxgbei_free_mext_pg);
+				if (__predict_false(m == NULL))
+					return (ENOMEM);
+				atomic_add_int(&icp->ref_cnt, 1);
+				m->m_ext.ext_arg1 = icp;
+				m->m_epg_1st_off = page_offset;
+			}
+
+			todo = MIN(len, PAGE_SIZE - page_offset);
+
+			m->m_epg_pa[m->m_epg_npgs] =
+			    VM_PAGE_TO_PHYS(bp->bio_ma[i]);
+			m->m_epg_npgs++;
+			m->m_epg_last_len = todo;
+			m->m_len += todo;
+			m->m_ext.ext_size += PAGE_SIZE;
+			MBUF_EXT_PGS_ASSERT_SANITY(m);
+
+			if (m->m_epg_npgs == MBUF_PEXT_MAX_PGS) {
+				if (m_tail != NULL)
+					m_tail->m_next = m;
+				else
+					ip->ip_data_mbuf = m;
+				m_tail = m;
+				ip->ip_data_len += m->m_len;
+				m = NULL;
+			}
+
+			page_offset = 0;
+			len -= todo;
+			i++;
+		}
+
+		if (m != NULL) {
+			if (m_tail != NULL)
+				m_tail->m_next = m;
+			else
+				ip->ip_data_mbuf = m;
+			ip->ip_data_len += m->m_len;
+		}
+		return (0);
+	}
+
+	m = cxgbei_getm(len, flags);
+	if (__predict_false(m == NULL))
+		return (ENOMEM);
+
+	if (ip->ip_data_mbuf == NULL) {
+		ip->ip_data_mbuf = m;
+		ip->ip_data_len = len;
+	} else {
+		m_tail->m_next = m;
+		ip->ip_data_len += len;
+	}
+
+	while (len > 0) {
+		todo = MIN(len, PAGE_SIZE - page_offset);
+
+		mapped = pmap_map_io_transient(bp->bio_ma + i, &vaddr, 1,
+		    FALSE);
+
+		do {
+			mtodo = min(todo, M_SIZE(m) - m->m_len);
+			memcpy(mtod(m, char *) + m->m_len, (char *)vaddr +
+			    page_offset, mtodo);
+			m->m_len += mtodo;
+			if (m->m_len == M_SIZE(m))
+				m = m->m_next;
+			page_offset += mtodo;
+			todo -= mtodo;
+		} while (todo > 0);
+
+		if (__predict_false(mapped))
+			pmap_unmap_io_transient(bp->bio_ma + 1, &vaddr, 1,
+			    FALSE);
+
+		page_offset = 0;
+		len -= todo;
+		i++;
+	}
+
+	MPASS(ip->ip_data_len <= max(ic->ic_max_send_data_segment_length,
+	    ic->ic_hw_isomax));
+
+	return (0);
+}
+
 int
 icl_cxgbei_conn_pdu_append_data(struct icl_conn *ic, struct icl_pdu *ip,
     const void *addr, size_t len, int flags)
@@ -592,58 +781,74 @@ icl_cxgbei_conn_pdu_append_data(struct icl_conn *ic, struct icl_pdu *ip,
 		return (0);
 	}
 
-	src = (const char *)addr;
+	m = cxgbei_getm(len, flags);
+	if (__predict_false(m == NULL))
+		return (ENOMEM);
 
-	/* Allocate as jumbo mbufs of size MJUM16BYTES. */
-	while (len >= MJUM16BYTES) {
-		m = m_getjcl(M_NOWAIT, MT_DATA, 0, MJUM16BYTES);
-		if (__predict_false(m == NULL)) {
-			if ((flags & M_WAITOK) != 0) {
-				/* Fall back to non-jumbo mbufs. */
-				break;
-			}
-			return (ENOMEM);
-		}
-		memcpy(mtod(m, void *), src, MJUM16BYTES);
-		m->m_len = MJUM16BYTES;
-		if (ip->ip_data_mbuf == NULL) {
-			ip->ip_data_mbuf = m_tail = m;
-			ip->ip_data_len = MJUM16BYTES;
-		} else {
-			m_tail->m_next = m;
-			m_tail = m_tail->m_next;
-			ip->ip_data_len += MJUM16BYTES;
-		}
-		src += MJUM16BYTES;
-		len -= MJUM16BYTES;
+	if (ip->ip_data_mbuf == NULL) {
+		ip->ip_data_mbuf = m;
+		ip->ip_data_len = len;
+	} else {
+		m_tail->m_next = m;
+		ip->ip_data_len += len;
 	}
-
-	/* Allocate mbuf chain for the remaining data. */
-	if (len != 0) {
-		m = m_getm2(NULL, len, flags, MT_DATA, 0);
-		if (__predict_false(m == NULL))
-			return (ENOMEM);
-		if (ip->ip_data_mbuf == NULL) {
-			ip->ip_data_mbuf = m;
-			ip->ip_data_len = len;
-		} else {
-			m_tail->m_next = m;
-			ip->ip_data_len += len;
-		}
-		for (; m != NULL; m = m->m_next) {
-			m->m_len = min(len, M_SIZE(m));
-			memcpy(mtod(m, void *), src, m->m_len);
-			src += m->m_len;
-			len -= m->m_len;
-		}
-		MPASS(len == 0);
+	src = (const char *)addr;
+	for (; m != NULL; m = m->m_next) {
+		m->m_len = min(len, M_SIZE(m));
+		memcpy(mtod(m, void *), src, m->m_len);
+		src += m->m_len;
+		len -= m->m_len;
 	}
+	MPASS(len == 0);
+
 	MPASS(ip->ip_data_len <= max(ic->ic_max_send_data_segment_length,
 	    ic->ic_hw_isomax));
 
 	return (0);
 }
 
+void
+icl_cxgbei_conn_pdu_get_bio(struct icl_conn *ic, struct icl_pdu *ip,
+    size_t pdu_off, struct bio *bp, size_t bio_off, size_t len)
+{
+	struct icl_cxgbei_pdu *icp = ip_to_icp(ip);
+	vm_offset_t vaddr;
+	size_t page_offset, todo;
+	boolean_t mapped;
+	int i;
+
+	if (icp->icp_flags & ICPF_RX_DDP)
+		return; /* data is DDP'ed, no need to copy */
+
+	MPASS(bp->bio_flags & BIO_UNMAPPED);
+	if (bio_off < PAGE_SIZE - bp->bio_ma_offset) {
+		page_offset = bp->bio_ma_offset + bio_off;
+		i = 0;
+	} else {
+		bio_off -= PAGE_SIZE - bp->bio_ma_offset;
+		for (i = 1; bio_off >= PAGE_SIZE; i++)
+			bio_off -= PAGE_SIZE;
+		page_offset = bio_off;
+	}
+
+	while (len > 0) {
+		todo = MIN(len, PAGE_SIZE - page_offset);
+
+		mapped = pmap_map_io_transient(bp->bio_ma + i, &vaddr, 1,
+		    FALSE);
+		m_copydata(ip->ip_data_mbuf, pdu_off, todo, (char *)vaddr +
+		    page_offset);
+		if (__predict_false(mapped))
+			pmap_unmap_io_transient(bp->bio_ma + 1, &vaddr, 1,
+			    FALSE);
+
+		page_offset = 0;
+		pdu_off += todo;
+		len -= todo;
+		i++;
+	}
+}
+
 void
 icl_cxgbei_conn_pdu_get_data(struct icl_conn *ic, struct icl_pdu *ip,
     size_t off, void *addr, size_t len)
@@ -716,7 +921,7 @@ icl_cxgbei_new_conn(const char *name, struct mtx *lock)
 #endif
 	ic->ic_name = name;
 	ic->ic_offload = "cxgbei";
-	ic->ic_unmapped = false;
+	ic->ic_unmapped = true;
 
 	CTR2(KTR_CXGBE, "%s: icc %p", __func__, icc);
 
@@ -1251,22 +1456,45 @@ no_ddp:
 	}
 	prsv = &ddp->prsv;
 
-	/* XXX add support for all CAM_DATA_ types */
-	MPASS((csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR);
-	rc = t4_alloc_page_pods_for_buf(pr, (vm_offset_t)csio->data_ptr,
-	    csio->dxfer_len, prsv);
-	if (rc != 0) {
-		free(ddp, M_CXGBEI);
-		goto no_ddp;
-	}
-
 	mbufq_init(&mq, INT_MAX);
-	rc = t4_write_page_pods_for_buf(sc, toep, prsv,
-	    (vm_offset_t)csio->data_ptr, csio->dxfer_len, &mq);
-	if (__predict_false(rc != 0)) {
-		mbufq_drain(&mq);
-		t4_free_page_pods(prsv);
+	switch (csio->ccb_h.flags & CAM_DATA_MASK) {
+	case CAM_DATA_BIO:
+		rc = t4_alloc_page_pods_for_bio(pr,
+		    (struct bio *)csio->data_ptr, prsv);
+		if (rc != 0) {
+			free(ddp, M_CXGBEI);
+			goto no_ddp;
+		}
+
+		rc = t4_write_page_pods_for_bio(sc, toep, prsv,
+		    (struct bio *)csio->data_ptr, &mq);
+		if (__predict_false(rc != 0)) {
+			mbufq_drain(&mq);
+			t4_free_page_pods(prsv);
+			free(ddp, M_CXGBEI);
+			goto no_ddp;
+		}
+		break;
+	case CAM_DATA_VADDR:
+		rc = t4_alloc_page_pods_for_buf(pr, (vm_offset_t)csio->data_ptr,
+		    csio->dxfer_len, prsv);
+		if (rc != 0) {
+			free(ddp, M_CXGBEI);
+			goto no_ddp;
+		}
+
+		rc = t4_write_page_pods_for_buf(sc, toep, prsv,
+		    (vm_offset_t)csio->data_ptr, csio->dxfer_len, &mq);
+		if (__predict_false(rc != 0)) {
+			mbufq_drain(&mq);
+			t4_free_page_pods(prsv);
+			free(ddp, M_CXGBEI);
+			goto no_ddp;
+		}
+		break;
+	default:
 		free(ddp, M_CXGBEI);
+		rc = EINVAL;
 		goto no_ddp;
 	}
 
diff --git a/sys/dev/cxgbe/tom/t4_cpl_io.c b/sys/dev/cxgbe/tom/t4_cpl_io.c
index f61fe557635c..42bcd7311fbe 100644
--- a/sys/dev/cxgbe/tom/t4_cpl_io.c
+++ b/sys/dev/cxgbe/tom/t4_cpl_io.c
@@ -1002,7 +1002,7 @@ write_iscsi_mbuf_wr(struct toepcb *toep, struct mbuf *sndptr)
 	int tx_credits, shove, npdu, wr_len;
 	uint16_t iso_mss;
 	static const u_int ulp_extra_len[] = {0, 4, 4, 8};
-	bool iso;
+	bool iso, nomap_mbuf_seen;
 
 	M_ASSERTPKTHDR(sndptr);
 
@@ -1030,8 +1030,15 @@ write_iscsi_mbuf_wr(struct toepcb *toep, struct mbuf *sndptr)
 	plen = 0;
 	nsegs = 0;
 	max_nsegs_1mbuf = 0; /* max # of SGL segments in any one mbuf */
+	nomap_mbuf_seen = false;
 	for (m = sndptr; m != NULL; m = m->m_next) {
-		int n = sglist_count(mtod(m, void *), m->m_len);
+		int n;
+
+		if (m->m_flags & M_EXTPG)
+			n = sglist_count_mbuf_epg(m, mtod(m, vm_offset_t),
+			    m->m_len);
+		else
+			n = sglist_count(mtod(m, void *), m->m_len);
 
 		nsegs += n;
 		plen += m->m_len;
@@ -1040,9 +1047,11 @@ write_iscsi_mbuf_wr(struct toepcb *toep, struct mbuf *sndptr)
 		 * This mbuf would send us _over_ the nsegs limit.
 		 * Suspend tx because the PDU can't be sent out.
 		 */
-		if (plen > max_imm && nsegs > max_nsegs)
+		if ((nomap_mbuf_seen || plen > max_imm) && nsegs > max_nsegs)
 			return (NULL);
 
+		if (m->m_flags & M_EXTPG)
+			nomap_mbuf_seen = true;
 		if (max_nsegs_1mbuf < n)
 			max_nsegs_1mbuf = n;
 	}
@@ -1075,7 +1084,7 @@ write_iscsi_mbuf_wr(struct toepcb *toep, struct mbuf *sndptr)
 	wr_len = sizeof(*txwr);
 	if (iso)
 		wr_len += sizeof(struct cpl_tx_data_iso);
-	if (plen <= max_imm) {
+	if (plen <= max_imm && !nomap_mbuf_seen) {
 		/* Immediate data tx */
 		imm_data = plen;
 		wr_len += plen;
diff --git a/sys/dev/cxgbe/tom/t4_ddp.c b/sys/dev/cxgbe/tom/t4_ddp.c
index 18c464b5c034..05bb903a28aa 100644
--- a/sys/dev/cxgbe/tom/t4_ddp.c
+++ b/sys/dev/cxgbe/tom/t4_ddp.c
@@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 #include <sys/aio.h>
+#include <sys/bio.h>
 #include <sys/file.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
@@ -887,14 +888,11 @@ alloc_page_pods(struct ppod_region *pr, u_int nppods, u_int pgsz_idx,
 	return (0);
 }
 
-int
-t4_alloc_page_pods_for_ps(struct ppod_region *pr, struct pageset *ps)
+static int
+t4_alloc_page_pods_for_vmpages(struct ppod_region *pr, vm_page_t *pages,
+    int npages, struct ppod_reservation *prsv)
 {
 	int i, hcf, seglen, idx, nppods;
-	struct ppod_reservation *prsv = &ps->prsv;
-
-	KASSERT(prsv->prsv_nppods == 0,
-	    ("%s: page pods already allocated", __func__));
 
 	/*
 	 * The DDP page size is unrelated to the VM page size.  We combine
@@ -904,11 +902,11 @@ t4_alloc_page_pods_for_ps(struct ppod_region *pr, struct pageset *ps)
 	 * the page list.
 	 */
 	hcf = 0;
-	for (i = 0; i < ps->npages; i++) {
+	for (i = 0; i < npages; i++) {
 		seglen = PAGE_SIZE;
-		while (i < ps->npages - 1 &&
-		    VM_PAGE_TO_PHYS(ps->pages[i]) + PAGE_SIZE ==
-		    VM_PAGE_TO_PHYS(ps->pages[i + 1])) {
+		while (i < npages - 1 &&
+		    VM_PAGE_TO_PHYS(pages[i]) + PAGE_SIZE ==
+		    VM_PAGE_TO_PHYS(pages[i + 1])) {
 			seglen += PAGE_SIZE;
 			i++;
 		}
@@ -931,12 +929,35 @@ t4_alloc_page_pods_for_ps(struct ppod_region *pr, struct pageset *ps)
 have_pgsz:
 	MPASS(idx <= M_PPOD_PGSZ);
 
-	nppods = pages_to_nppods(ps->npages, pr->pr_page_shift[idx]);
+	nppods = pages_to_nppods(npages, pr->pr_page_shift[idx]);
 	if (alloc_page_pods(pr, nppods, idx, prsv) != 0)
-		return (0);
+		return (ENOMEM);
 	MPASS(prsv->prsv_nppods > 0);
 
-	return (1);
+	return (0);
+}
+
+int
+t4_alloc_page_pods_for_ps(struct ppod_region *pr, struct pageset *ps)
+{
+	struct ppod_reservation *prsv = &ps->prsv;
+
+	KASSERT(prsv->prsv_nppods == 0,
+	    ("%s: page pods already allocated", __func__));
+
+	return (t4_alloc_page_pods_for_vmpages(pr, ps->pages, ps->npages,
+	    prsv));
+}
+
+int
+t4_alloc_page_pods_for_bio(struct ppod_region *pr, struct bio *bp,
+    struct ppod_reservation *prsv)
+{
+
+	MPASS(bp->bio_flags & BIO_UNMAPPED);
+
+	return (t4_alloc_page_pods_for_vmpages(pr, bp->bio_ma, bp->bio_ma_n,
+	    prsv));
 }
 
 int
@@ -1189,6 +1210,83 @@ alloc_raw_wr_mbuf(int len)
 	return (m);
 }
 
+int
+t4_write_page_pods_for_bio(struct adapter *sc, struct toepcb *toep,
+    struct ppod_reservation *prsv, struct bio *bp, struct mbufq *wrq)
+{
+	struct ulp_mem_io *ulpmc;
+	struct ulptx_idata *ulpsc;
+	struct pagepod *ppod;
+	int i, j, k, n, chunk, len, ddp_pgsz, idx;
+	u_int ppod_addr;
+	uint32_t cmd;
+	struct ppod_region *pr = prsv->prsv_pr;
+	vm_paddr_t pa;
+	struct mbuf *m;
+
+	MPASS(bp->bio_flags & BIO_UNMAPPED);
+
+	cmd = htobe32(V_ULPTX_CMD(ULP_TX_MEM_WRITE));
+	if (is_t4(sc))
+		cmd |= htobe32(F_ULP_MEMIO_ORDER);
+	else
+		cmd |= htobe32(F_T5_ULP_MEMIO_IMM);
+	ddp_pgsz = 1 << pr->pr_page_shift[G_PPOD_PGSZ(prsv->prsv_tag)];
+	ppod_addr = pr->pr_start + (prsv->prsv_tag & pr->pr_tag_mask);
+	for (i = 0; i < prsv->prsv_nppods; ppod_addr += chunk) {
+
+		/* How many page pods are we writing in this cycle */
+		n = min(prsv->prsv_nppods - i, NUM_ULP_TX_SC_IMM_PPODS);
+		MPASS(n > 0);
+		chunk = PPOD_SZ(n);
+		len = roundup2(sizeof(*ulpmc) + sizeof(*ulpsc) + chunk, 16);
+
+		m = alloc_raw_wr_mbuf(len);
+		if (m == NULL)
+			return (ENOMEM);
+
+		ulpmc = mtod(m, struct ulp_mem_io *);
+		INIT_ULPTX_WR(ulpmc, len, 0, toep->tid);
+		ulpmc->cmd = cmd;
+		ulpmc->dlen = htobe32(V_ULP_MEMIO_DATA_LEN(chunk / 32));
+		ulpmc->len16 = htobe32(howmany(len - sizeof(ulpmc->wr), 16));
+		ulpmc->lock_addr = htobe32(V_ULP_MEMIO_ADDR(ppod_addr >> 5));
+
+		ulpsc = (struct ulptx_idata *)(ulpmc + 1);
+		ulpsc->cmd_more = htobe32(V_ULPTX_CMD(ULP_TX_SC_IMM));
+		ulpsc->len = htobe32(chunk);
+
+		ppod = (struct pagepod *)(ulpsc + 1);
+		for (j = 0; j < n; i++, j++, ppod++) {
+			ppod->vld_tid_pgsz_tag_color = htobe64(F_PPOD_VALID |
+			    V_PPOD_TID(toep->tid) |
+			    (prsv->prsv_tag & ~V_PPOD_PGSZ(M_PPOD_PGSZ)));
+			ppod->len_offset = htobe64(V_PPOD_LEN(bp->bio_bcount) |
+			    V_PPOD_OFST(bp->bio_ma_offset));
+			ppod->rsvd = 0;
+			idx = i * PPOD_PAGES * (ddp_pgsz / PAGE_SIZE);
+			for (k = 0; k < nitems(ppod->addr); k++) {
+				if (idx < bp->bio_ma_n) {
+					pa = VM_PAGE_TO_PHYS(bp->bio_ma[idx]);
+					ppod->addr[k] = htobe64(pa);
+					idx += ddp_pgsz / PAGE_SIZE;
+				} else
+					ppod->addr[k] = 0;
+#if 0
+				CTR5(KTR_CXGBE,
+				    "%s: tid %d ppod[%d]->addr[%d] = %p",
+				    __func__, toep->tid, i, k,
+				    be64toh(ppod->addr[k]));
+#endif
+			}
+		}
+
+		mbufq_enqueue(wrq, m);
+	}
+
+	return (0);
+}
+
 int
 t4_write_page_pods_for_buf(struct adapter *sc, struct toepcb *toep,
     struct ppod_reservation *prsv, vm_offset_t buf, int buflen,
@@ -1398,7 +1496,7 @@ prep_pageset(struct adapter *sc, struct toepcb *toep, struct pageset *ps)
 	struct tom_data *td = sc->tom_softc;
 
 	if (ps->prsv.prsv_nppods == 0 &&
-	    !t4_alloc_page_pods_for_ps(&td->pr, ps)) {
+	    t4_alloc_page_pods_for_ps(&td->pr, ps) != 0) {
 		return (0);
 	}
 	if (!(ps->flags & PS_PPODS_WRITTEN) &&
diff --git a/sys/dev/cxgbe/tom/t4_tom.h b/sys/dev/cxgbe/tom/t4_tom.h
index 2224ece20c38..c567541fd788 100644
--- a/sys/dev/cxgbe/tom/t4_tom.h
+++ b/sys/dev/cxgbe/tom/t4_tom.h
@@ -89,6 +89,7 @@ enum {
 	DDP_DEAD	= (1 << 6),	/* toepcb is shutting down */
 };
 
+struct bio;
 struct ctl_sg_entry;
 struct sockopt;
 struct offload_settings;
@@ -489,12 +490,16 @@ int t4_init_ppod_region(struct ppod_region *, struct t4_range *, u_int,
     const char *);
 void t4_free_ppod_region(struct ppod_region *);
 int t4_alloc_page_pods_for_ps(struct ppod_region *, struct pageset *);
+int t4_alloc_page_pods_for_bio(struct ppod_region *, struct bio *,
+    struct ppod_reservation *);
 int t4_alloc_page_pods_for_buf(struct ppod_region *, vm_offset_t, int,
     struct ppod_reservation *);
 int t4_alloc_page_pods_for_sgl(struct ppod_region *, struct ctl_sg_entry *, int,
     struct ppod_reservation *);
 int t4_write_page_pods_for_ps(struct adapter *, struct sge_wrq *, int,
     struct pageset *);
+int t4_write_page_pods_for_bio(struct adapter *, struct toepcb *,
+    struct ppod_reservation *, struct bio *, struct mbufq *);
 int t4_write_page_pods_for_buf(struct adapter *, struct toepcb *,
     struct ppod_reservation *, vm_offset_t, int, struct mbufq *);
 int t4_write_page_pods_for_sgl(struct adapter *, struct toepcb *,