diff --git a/sys/dev/cxgbe/cxgbei/cxgbei.h b/sys/dev/cxgbe/cxgbei/cxgbei.h --- a/sys/dev/cxgbe/cxgbei/cxgbei.h +++ b/sys/dev/cxgbe/cxgbei/cxgbei.h @@ -96,6 +96,10 @@ uint32_t icp_signature; uint32_t icp_seq; /* For debug only */ u_int icp_flags; + + u_int ref_cnt; + icl_pdu_cb cb; + int error; }; static inline struct icl_cxgbei_pdu * diff --git a/sys/dev/cxgbe/cxgbei/icl_cxgbei.c b/sys/dev/cxgbe/cxgbei/icl_cxgbei.c --- a/sys/dev/cxgbe/cxgbei/icl_cxgbei.c +++ b/sys/dev/cxgbe/cxgbei/icl_cxgbei.c @@ -100,6 +100,8 @@ #include "tom/t4_tom.h" #include "cxgbei.h" +static MALLOC_DEFINE(M_CXGBEI, "cxgbei", "cxgbei(4)"); + SYSCTL_NODE(_kern_icl, OID_AUTO, cxgbei, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Chelsio iSCSI offload"); static int coalesce = 1; @@ -130,6 +132,7 @@ static icl_conn_pdu_append_data_t icl_cxgbei_conn_pdu_append_data; 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; static icl_conn_handoff_t icl_cxgbei_conn_handoff; static icl_conn_free_t icl_cxgbei_conn_free; static icl_conn_close_t icl_cxgbei_conn_close; @@ -146,6 +149,7 @@ KOBJMETHOD(icl_conn_pdu_append_data, icl_cxgbei_conn_pdu_append_data), 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), KOBJMETHOD(icl_conn_handoff, icl_cxgbei_conn_handoff), KOBJMETHOD(icl_conn_free, icl_cxgbei_conn_free), KOBJMETHOD(icl_conn_close, icl_cxgbei_conn_close), @@ -161,54 +165,108 @@ void icl_cxgbei_conn_pdu_free(struct icl_conn *ic, struct icl_pdu *ip) { -#ifdef INVARIANTS struct icl_cxgbei_pdu *icp = ip_to_icp(ip); -#endif + KASSERT(icp->ref_cnt != 0, ("freeing deleted PDU")); MPASS(icp->icp_signature == CXGBEI_PDU_SIGNATURE); MPASS(ic == ip->ip_conn); - MPASS(ip->ip_bhs_mbuf != NULL); m_freem(ip->ip_ahs_mbuf); m_freem(ip->ip_data_mbuf); - m_freem(ip->ip_bhs_mbuf); /* storage for icl_cxgbei_pdu itself */ + m_freem(ip->ip_bhs_mbuf); + + KASSERT(ic != NULL || icp->ref_cnt == 1, + ("orphaned PDU has oustanding references")); + if (atomic_fetchadd_int(&icp->ref_cnt, -1) != 1) + return; + + free(icp, M_CXGBEI); #ifdef DIAGNOSTIC if (__predict_true(ic != NULL)) refcount_release(&ic->ic_outstanding_pdus); #endif } +static void +icl_cxgbei_pdu_call_cb(struct icl_pdu *ip) +{ + struct icl_cxgbei_pdu *icp = ip_to_icp(ip); + + MPASS(icp->icp_signature == CXGBEI_PDU_SIGNATURE); + + if (icp->cb != NULL) + icp->cb(ip, icp->error); +#ifdef DIAGNOSTIC + if (__predict_true(ip->ip_conn != NULL)) + refcount_release(&ip->ip_conn->ic_outstanding_pdus); +#endif + free(icp, M_CXGBEI); +} + +static void +icl_cxgbei_pdu_done(struct icl_pdu *ip, int error) +{ + struct icl_cxgbei_pdu *icp = ip_to_icp(ip); + + if (error != 0) + icp->error = error; + + m_freem(ip->ip_ahs_mbuf); + ip->ip_ahs_mbuf = NULL; + m_freem(ip->ip_data_mbuf); + ip->ip_data_mbuf = NULL; + m_freem(ip->ip_bhs_mbuf); + ip->ip_bhs_mbuf = NULL; + + /* + * All other references to this PDU should have been dropped + * by the m_freem() of ip_data_mbuf. + */ + if (atomic_fetchadd_int(&icp->ref_cnt, -1) == 1) + icl_cxgbei_pdu_call_cb(ip); + else + __assert_unreachable(); +} + +static void +icl_cxgbei_mbuf_done(struct mbuf *mb) +{ + + struct icl_cxgbei_pdu *icp = (struct icl_cxgbei_pdu *)mb->m_ext.ext_arg1; + + /* + * NB: mb_free_mext() might leave ref_cnt as 1 without + * decrementing it if it hits the fast path in the ref_cnt + * check. + */ + icl_cxgbei_pdu_call_cb(&icp->ip); +} + struct icl_pdu * icl_cxgbei_new_pdu(int flags) { struct icl_cxgbei_pdu *icp; struct icl_pdu *ip; struct mbuf *m; - uintptr_t a; - m = m_gethdr(flags, MT_DATA); - if (__predict_false(m == NULL)) + icp = malloc(sizeof(*icp), M_CXGBEI, flags | M_ZERO); + if (__predict_false(icp == NULL)) return (NULL); - a = roundup2(mtod(m, uintptr_t), _Alignof(struct icl_cxgbei_pdu)); - icp = (struct icl_cxgbei_pdu *)a; - bzero(icp, sizeof(*icp)); - icp->icp_signature = CXGBEI_PDU_SIGNATURE; + icp->ref_cnt = 1; ip = &icp->ip; - ip->ip_bhs_mbuf = m; - a = roundup2((uintptr_t)(icp + 1), _Alignof(struct iscsi_bhs *)); - ip->ip_bhs = (struct iscsi_bhs *)a; -#ifdef INVARIANTS - /* Everything must fit entirely in the mbuf. */ - a = (uintptr_t)(ip->ip_bhs + 1); - MPASS(a <= (uintptr_t)m + MSIZE); -#endif - bzero(ip->ip_bhs, sizeof(*ip->ip_bhs)); + m = m_gethdr(flags, MT_DATA); + if (__predict_false(m == NULL)) { + free(icp, M_CXGBEI); + return (NULL); + } - m->m_data = (void *)ip->ip_bhs; + ip->ip_bhs_mbuf = m; + ip->ip_bhs = mtod(m, struct iscsi_bhs *); + memset(ip->ip_bhs, 0, sizeof(*ip->ip_bhs)); m->m_len = sizeof(struct iscsi_bhs); m->m_pkthdr.len = m->m_len; @@ -306,16 +364,22 @@ bhs->bhs_data_segment_len[1] = ip->ip_data_len >> 8; bhs->bhs_data_segment_len[0] = ip->ip_data_len >> 16; - /* "Convert" PDU to mbuf chain. Do not use icp/ip after this. */ - m->m_pkthdr.len = sizeof(struct iscsi_bhs) + ip->ip_data_len + padding; + /* + * Extract mbuf chain from PDU. + */ + m->m_pkthdr.len += ip->ip_data_len + padding; m->m_next = ip->ip_data_mbuf; set_mbuf_ulp_submode(m, ulp_submode); -#ifdef INVARIANTS - bzero(icp, sizeof(*icp)); -#endif -#ifdef DIAGNOSTIC - refcount_release(&icc->ic.ic_outstanding_pdus); -#endif + ip->ip_bhs_mbuf = NULL; + ip->ip_data_mbuf = NULL; + ip->ip_bhs = NULL; + + /* + * Drop PDU reference on icp. Additional references might + * still be held by zero-copy PDU buffers (ICL_NOCOPY). + */ + if (atomic_fetchadd_int(&icp->ref_cnt, -1) == 1) + icl_cxgbei_pdu_call_cb(ip); return (m); } @@ -324,9 +388,7 @@ icl_cxgbei_conn_pdu_append_data(struct icl_conn *ic, struct icl_pdu *ip, const void *addr, size_t len, int flags) { -#ifdef INVARIANTS struct icl_cxgbei_pdu *icp = ip_to_icp(ip); -#endif struct mbuf *m, *m_tail; const char *src; @@ -339,6 +401,29 @@ for (; m_tail->m_next != NULL; m_tail = m_tail->m_next) ; + if (flags & ICL_NOCOPY) { + m = m_get(flags & ~ICL_NOCOPY, MT_DATA); + if (m == NULL) { + ICL_WARN("failed to allocate mbuf"); + return (ENOMEM); + } + + m->m_flags |= M_RDONLY; + m_extaddref(m, __DECONST(char *, addr), len, &icp->ref_cnt, + icl_cxgbei_mbuf_done, icp, NULL); + m->m_len = len; + if (ip->ip_data_mbuf == NULL) { + ip->ip_data_mbuf = m; + ip->ip_data_len = len; + } else { + m_tail->m_next = m; + m_tail = m_tail->m_next; + ip->ip_data_len += len; + } + + return (0); + } + src = (const char *)addr; /* Allocate as jumbo mbufs of size MJUM16BYTES. */ @@ -403,6 +488,13 @@ void icl_cxgbei_conn_pdu_queue(struct icl_conn *ic, struct icl_pdu *ip) +{ + icl_cxgbei_conn_pdu_queue_cb(ic, ip, NULL); +} + +void +icl_cxgbei_conn_pdu_queue_cb(struct icl_conn *ic, struct icl_pdu *ip, + icl_pdu_cb cb) { struct epoch_tracker et; struct icl_cxgbei_conn *icc = ic_to_icc(ic); @@ -418,9 +510,12 @@ MPASS(ip->ip_ahs_mbuf == NULL && ip->ip_ahs_len == 0); ICL_CONN_LOCK_ASSERT(ic); + + icp->cb = cb; + /* NOTE: sowriteable without so_snd lock is a mostly harmless race. */ if (ic->ic_disconnecting || so == NULL || !sowriteable(so)) { - icl_cxgbei_conn_pdu_free(ic, ip); + icl_cxgbei_pdu_done(ip, ENOTCONN); return; } @@ -809,7 +904,7 @@ while (!STAILQ_EMPTY(&icc->rcvd_pdus)) { ip = STAILQ_FIRST(&icc->rcvd_pdus); STAILQ_REMOVE_HEAD(&icc->rcvd_pdus, ip_next); - icl_cxgbei_conn_pdu_free(ic, ip); + icl_cxgbei_pdu_done(ip, ENOTCONN); } SOCKBUF_UNLOCK(sb); }