1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26/*
27 * This file implements the Directed Route (DR) loopback support in IBMF.
28 */
29
30#include <sys/ib/mgt/ibmf/ibmf_impl.h>
31#include <sys/ib/mgt/ib_mad.h>
32
33#define	MELLANOX_VENDOR	0x15b3
34
35extern int ibmf_trace_level;
36
37static int ibmf_i_dr_loopback_filter(ibmf_client_t *clientp,
38    ibmf_msg_impl_t *msgimplp, int blocking);
39static void ibmf_i_dr_loopback_term(ibmf_client_t *clientp,
40    ibmf_msg_impl_t *msgimplp, int blocking);
41
42/*
43 * ibmf_i_check_for_loopback():
44 *	Check for DR loopback traffic
45 */
46int
47ibmf_i_check_for_loopback(ibmf_msg_impl_t *msgimplp, ibmf_msg_cb_t msg_cb,
48    void *msg_cb_args, ibmf_retrans_t *retrans, boolean_t *loopback)
49{
50	sm_dr_mad_hdr_t	*dr_hdr;
51	boolean_t	blocking;
52	int		status;
53	ibmf_ci_t	*cip = ((ibmf_client_t *)msgimplp->im_client)->ic_myci;
54
55	IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L4,
56	    ibmf_i_check_for_loopback_start, IBMF_TNF_TRACE, "",
57	    "ibmf_i_check_for_loopback() enter, msg = 0x%p\n",
58	    tnf_opaque, msg, msgimplp);
59
60	*loopback = B_FALSE;
61	dr_hdr = (sm_dr_mad_hdr_t *)msgimplp->im_msgbufs_send.im_bufs_mad_hdr;
62
63	/*
64	 * Some HCAs do not handle directed route loopback MADs.
65	 * Such MADs are sent out on the wire instead of being looped back.
66	 * This behavior causes the SM to hang since the SM starts
67	 * its sweep with loopback DR MADs.
68	 * This ibmf workaround does the loopback without passing the MAD
69	 * into the transport layer.
70	 * We should really check a property of the hardware to determine
71	 * whether or not an IB HCA can "hear" itself rather than
72	 * checking for specific HCAs or vendor of HCAs.
73	 */
74	if ((dr_hdr->MgmtClass == MAD_MGMT_CLASS_SUBN_DIRECT_ROUTE) &&
75	    (dr_hdr->HopCount == 0) && (cip->ci_vendor_id == MELLANOX_VENDOR)) {
76		if (msg_cb == NULL) {
77			blocking = B_TRUE;
78		} else {
79			blocking = B_FALSE;
80		}
81
82		ibmf_i_init_msg(msgimplp, msg_cb, msg_cb_args, retrans,
83		    blocking);
84
85		status = ibmf_i_dr_loopback_filter(msgimplp->im_client,
86		    msgimplp, blocking);
87		if (status != IBMF_SUCCESS) {
88			IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
89			    ibmf_i_check_for_loopback_err,
90			    "ibmf_i_check_for_loopback(): %s\n",
91			    IBMF_TNF_ERROR, "", tnf_string, msg,
92			    "Failure in DR loopback filter");
93			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
94			    ibmf_i_check_for_loopback_end, IBMF_TNF_TRACE, "",
95			    "ibmf_i_check_for_loopback() exit\n");
96			return (status);
97		}
98
99		*loopback = B_TRUE;
100	}
101
102	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4, ibmf_i_check_for_loopback_end,
103	    IBMF_TNF_TRACE, "", "ibmf_i_check_for_loopback() exit\n");
104
105	return (IBMF_SUCCESS);
106
107}
108
109/*
110 * ibmf_i_dr_loopback_term():
111 *	Perform termination processing of a DR loopback transaction
112 */
113static void
114ibmf_i_dr_loopback_term(ibmf_client_t *clientp, ibmf_msg_impl_t *msgimplp,
115    int blocking)
116{
117	uint_t refcnt;
118
119	IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L4,
120	    ibmf_i_dr_loopback_term_start, IBMF_TNF_TRACE, "",
121	    "ibmf_i_dr_loopback_term() enter, clientp = 0x%p, msg = 0x%p\n",
122	    tnf_opaque, clientp, clientp, tnf_opaque, msg, msgimplp);
123
124	mutex_enter(&msgimplp->im_mutex);
125
126	if (blocking) {
127		/*
128		 * For sequenced, and blocking transactions, we wait for
129		 * the response. For non-sequenced, and blocking transactions,
130		 * we are done since the send has completed (no send completion
131		 * as when calling into IBTF).
132		 */
133		if ((msgimplp->im_flags & IBMF_MSG_FLAGS_SEQUENCED) &&
134		    ((msgimplp->im_trans_state_flags &
135		    IBMF_TRANS_STATE_FLAG_SIGNALED) == 0)) {
136
137			msgimplp->im_trans_state_flags |=
138			    IBMF_TRANS_STATE_FLAG_WAIT;
139
140			IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
141			    ibmf_i_dr_loopback, IBMF_TNF_TRACE, "",
142			    "ibmf_i_dr_loopback_term(): %s\n",
143			    tnf_string, msg, "Blocking for completion");
144
145			cv_wait(&msgimplp->im_trans_cv, &msgimplp->im_mutex);
146
147			msgimplp->im_trans_state_flags &=
148			    ~IBMF_TRANS_STATE_FLAG_WAIT;
149
150			msgimplp->im_flags &= ~IBMF_MSG_FLAGS_BUSY;
151
152			mutex_exit(&msgimplp->im_mutex);
153
154		} else if ((msgimplp->im_flags &
155		    IBMF_MSG_FLAGS_SEQUENCED) == 0) {
156
157			msgimplp->im_trans_state_flags |=
158			    IBMF_TRANS_STATE_FLAG_DONE;
159			msgimplp->im_flags &= ~IBMF_MSG_FLAGS_BUSY;
160
161			mutex_exit(&msgimplp->im_mutex);
162
163			ibmf_i_client_rem_msg(clientp, msgimplp, &refcnt);
164		} else {
165
166			msgimplp->im_flags &= ~IBMF_MSG_FLAGS_BUSY;
167			mutex_exit(&msgimplp->im_mutex);
168		}
169
170	} else if ((msgimplp->im_flags & IBMF_MSG_FLAGS_SEQUENCED) == 0) {
171
172		IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L3,
173		    ibmf_i_dr_loopback, IBMF_TNF_TRACE, "",
174		    "ibmf_i_dr_loopback_term(): %s\n",
175		    tnf_string, msg, "Not sequenced, returning to caller");
176		msgimplp->im_trans_state_flags |= IBMF_TRANS_STATE_FLAG_DONE;
177		msgimplp->im_flags &= ~IBMF_MSG_FLAGS_BUSY;
178		mutex_exit(&msgimplp->im_mutex);
179
180		ibmf_i_client_rem_msg(clientp, msgimplp, &refcnt);
181
182		if (msgimplp->im_trans_cb) {
183			msgimplp->im_trans_cb((ibmf_handle_t)clientp,
184			    (ibmf_msg_t *)msgimplp, msgimplp->im_trans_cb_arg);
185		}
186	} else {
187
188		msgimplp->im_flags &= ~IBMF_MSG_FLAGS_BUSY;
189		mutex_exit(&msgimplp->im_mutex);
190	}
191
192	IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4, ibmf_i_dr_loopback_term_end,
193	    IBMF_TNF_TRACE, "", "ibmf_i_dr_loopback_term() exit\n");
194
195}
196
197/*
198 * ibmf_i_dr_loopback_filter():
199 * This function intercepts Directed Route MADs with zero hop count,
200 * or loopback DR MADs. If the MAD is outbound from the SM, the SMA's
201 * client handle is located, and the receive callback invoked.
202 * If the MAD is outbound from the SMA, the SM's client handle is located
203 * and the receive callback invoked.
204 *
205 * This filtering is needed for some HCAs where the SMA cannot handle DR
206 * MAD's that need to be treated as a loopback MAD. On these HCAs, we see
207 * the zero hopcount MAD being sent out on the wire which it should not.
208 */
209static int
210ibmf_i_dr_loopback_filter(ibmf_client_t *clientp, ibmf_msg_impl_t *msgimplp,
211    int blocking)
212{
213	ibmf_client_t	*rclientp;
214	sm_dr_mad_hdr_t	*dr_hdr;
215	ibmf_msg_impl_t	*rmsgimplp;
216	boolean_t	rbuf_alloced;
217	int		msg_trans_state_flags, msg_flags;
218	uint_t		ref_cnt;
219	int		ret;
220
221	IBMF_TRACE_2(IBMF_TNF_DEBUG, DPRINT_L4,
222	    ibmf_i_dr_loopback_filter_start, IBMF_TNF_TRACE, "",
223	    "ibmf_i_dr_loopback_filter() enter, clientp = 0x%p, msg = 0x%p\n",
224	    tnf_opaque, clientp, clientp, tnf_opaque, msg, msgimplp);
225
226	dr_hdr = (sm_dr_mad_hdr_t *)msgimplp->im_msgbufs_send.im_bufs_mad_hdr;
227
228	/* set transaction flag for a sequenced transaction */
229	if (msgimplp->im_transp_op_flags & IBMF_MSG_TRANS_FLAG_SEQ)
230		msgimplp->im_flags |= IBMF_MSG_FLAGS_SEQUENCED;
231
232	/*
233	 * If the DR SMP method is a Get or a Set, the target is the SMA, else,
234	 * if the method is a GetResponse, the target is the SM. If the
235	 * Attribute is SMInfo, the target is always the SM.
236	 */
237	if ((((dr_hdr->R_Method == MAD_METHOD_GET) ||
238	    (dr_hdr->R_Method == MAD_METHOD_SET)) &&
239	    (dr_hdr->AttributeID != SM_SMINFO_ATTRID)) ||
240	    (dr_hdr->R_Method == MAD_METHOD_TRAP_REPRESS)) {
241
242		ret = ibmf_i_lookup_client_by_mgmt_class(clientp->ic_myci,
243		    clientp->ic_client_info.port_num, SUBN_AGENT, &rclientp);
244		if (ret != IBMF_SUCCESS) {
245			IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
246			    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
247			    "ibmf_i_dr_loopback_filter(): %s\n",
248			    tnf_string, msg,
249			    "Client for Mgt Class Subnet Agent not found");
250			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
251			    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
252			    "ibmf_i_dr_loopback_filter() exit\n");
253			return (ret);
254		}
255
256	} else if ((dr_hdr->R_Method == MAD_METHOD_GET_RESPONSE) ||
257	    (dr_hdr->R_Method == MAD_METHOD_TRAP) ||
258	    (dr_hdr->AttributeID == SM_SMINFO_ATTRID)) {
259
260		ret = ibmf_i_lookup_client_by_mgmt_class(clientp->ic_myci,
261		    clientp->ic_client_info.port_num, SUBN_MANAGER, &rclientp);
262		if (ret != IBMF_SUCCESS) {
263			IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
264			    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
265			    "ibmf_i_dr_loopback_filter(): %s\n",
266			    tnf_string, msg,
267			    "Client for Mgt Class Subnet Manager not found")
268			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
269			    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
270			    "ibmf_i_dr_loopback_filter() exit\n");
271			return (ret);
272		}
273	} else {
274		IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
275		    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
276		    "ibmf_i_dr_loopback_filter(): %s, method = 0x%x\n",
277		    tnf_string, msg, "Unexpected dr method",
278		    tnf_opaque, method, dr_hdr->R_Method);
279		IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
280		    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
281		    "ibmf_i_dr_loopback_filter() exit\n");
282
283		return (IBMF_FAILURE);
284	}
285
286	/*
287	 * Initialize the Transaction ID and Mgmt Class fields in the
288	 * message context.
289	 * NOTE: The IB MAD header in the incoming MAD is in wire (big-endian)
290	 * format and needs to be converted to the host endian format where
291	 * applicable (multi-byte fields)
292	 */
293	msgimplp->im_tid	= b2h64(dr_hdr->TransactionID);
294	msgimplp->im_mgt_class 	= dr_hdr->MgmtClass;
295
296	/*
297	 * Find the message context in the target client corresponding to the
298	 * transaction ID and management class in the source message context
299	 */
300	rmsgimplp = ibmf_i_find_msg(rclientp, msgimplp->im_tid,
301	    dr_hdr->MgmtClass, dr_hdr->R_Method,
302	    msgimplp->im_local_addr.ia_remote_lid, NULL, B_FALSE, NULL,
303	    IBMF_REG_MSG_LIST);
304
305	if (rmsgimplp != NULL) {
306
307		mutex_enter(&rmsgimplp->im_mutex);
308
309		/*
310		 * If the message has been marked unitialized or done
311		 * release the message mutex and return
312		 */
313		if ((rmsgimplp->im_trans_state_flags &
314		    IBMF_TRANS_STATE_FLAG_DONE) ||
315		    (rmsgimplp->im_trans_state_flags &
316		    IBMF_TRANS_STATE_FLAG_UNINIT)) {
317			IBMF_MSG_DECR_REFCNT(rmsgimplp);
318			msg_trans_state_flags = rmsgimplp->im_trans_state_flags;
319			msg_flags = rmsgimplp->im_flags;
320			ref_cnt = rmsgimplp->im_ref_count;
321			mutex_exit(&rmsgimplp->im_mutex);
322			/*
323			 * This thread may notify the client only if the
324			 * transaction is done, the message has been removed
325			 * from the client's message list, and the message
326			 * reference count is 0.
327			 * If the transaction is done, and the message reference
328			 * count = 0, there is still a possibility that a
329			 * packet could arrive for the message and its reference
330			 * count increased if the message is still on the list.
331			 * If the message is still on the list, it will be
332			 * removed by a call to ibmf_i_client_rem_msg() at
333			 * the completion point of the transaction.
334			 * So, the reference count should be checked after the
335			 * message has been removed.
336			 */
337			if ((msg_trans_state_flags &
338			    IBMF_TRANS_STATE_FLAG_DONE) &&
339			    !(msg_flags & IBMF_MSG_FLAGS_ON_LIST) &&
340			    (ref_cnt == 0)) {
341				ibmf_i_notify_client(rmsgimplp);
342			}
343			IBMF_TRACE_2(IBMF_TNF_NODEBUG, DPRINT_L1,
344			    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
345			    "ibmf_i_dr_loopback_filter(): %s, msg = 0x%p\n",
346			    tnf_string, msg,
347			    "Message already marked for removal, dropping MAD",
348			    tnf_opaque, msgimplp, msgimplp);
349			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
350			    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
351			    "ibmf_i_dr_loopback_filter() exit\n");
352			return (IBMF_FAILURE);
353		}
354	} else {
355
356		_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*rmsgimplp))
357
358		/* This is an unsolicited message */
359
360		rmsgimplp = (ibmf_msg_impl_t *)kmem_zalloc(
361		    sizeof (ibmf_msg_impl_t), KM_NOSLEEP);
362		if (rmsgimplp == NULL) {
363			IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
364			    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
365			    "ibmf_i_dr_loopback_filter(): %s\n",
366			    tnf_string, msg, "Failed to alloc packet");
367			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
368			    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
369			    "ibmf_i_dr_loopback_filter() exit\n");
370			return (IBMF_NO_RESOURCES);
371		}
372
373		mutex_init(&rmsgimplp->im_mutex, NULL, MUTEX_DRIVER, NULL);
374
375		rmsgimplp->im_client	= rclientp;
376		rmsgimplp->im_qp_hdl	= msgimplp->im_qp_hdl;
377		rmsgimplp->im_unsolicited = B_TRUE;
378		rmsgimplp->im_tid 	= b2h64(dr_hdr->TransactionID);
379		rmsgimplp->im_mgt_class	= dr_hdr->MgmtClass;
380
381		/* indicate the client callback is active */
382		if (rmsgimplp->im_qp_hdl == IBMF_QP_HANDLE_DEFAULT) {
383			mutex_enter(&rclientp->ic_mutex);
384			IBMF_RECV_CB_SETUP(rclientp);
385			mutex_exit(&rclientp->ic_mutex);
386		} else {
387			ibmf_alt_qp_t *qpp;
388
389			qpp = (ibmf_alt_qp_t *)rmsgimplp->im_qp_hdl;
390			mutex_enter(&qpp->isq_mutex);
391			IBMF_ALT_RECV_CB_SETUP(qpp);
392			mutex_exit(&qpp->isq_mutex);
393		}
394
395		/* Increment the message reference count */
396		IBMF_MSG_INCR_REFCNT(rmsgimplp);
397		rmsgimplp->im_trans_state_flags = IBMF_TRANS_STATE_FLAG_UNINIT;
398
399		_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*rmsgimplp))
400
401		/* add message to client's list; will acquire im_mutex */
402		ibmf_i_client_add_msg(rclientp, rmsgimplp);
403
404		mutex_enter(&rmsgimplp->im_mutex);
405
406		/* no one should have touched our state */
407		ASSERT(rmsgimplp->im_trans_state_flags ==
408		    IBMF_TRANS_STATE_FLAG_UNINIT);
409
410		/* transition out of uninit state */
411		rmsgimplp->im_trans_state_flags = IBMF_TRANS_STATE_FLAG_INIT;
412	}
413
414	/* Allocate memory for the receive buffers */
415	if (rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr == NULL) {
416		rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr =
417		    (ib_mad_hdr_t *)kmem_zalloc(IBMF_MAD_SIZE, KM_NOSLEEP);
418		if (rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr == NULL) {
419			IBMF_MSG_DECR_REFCNT(rmsgimplp);
420			mutex_exit(&rmsgimplp->im_mutex);
421			kmem_free(rmsgimplp, sizeof (ibmf_msg_impl_t));
422			IBMF_TRACE_1(IBMF_TNF_NODEBUG, DPRINT_L1,
423			    ibmf_recv_pkt_cb_err, IBMF_TNF_TRACE, "",
424			    "ibmf_i_dr_loopback_filter(): %s\n",
425			    tnf_string, msg, "mem allocation failure");
426			IBMF_TRACE_0(IBMF_TNF_DEBUG, DPRINT_L4,
427			    ibmf_i_dr_loopback_filter_end, IBMF_TNF_TRACE, "",
428			    "ibmf_i_dr_loopback_filter() exit\n");
429			return (IBMF_NO_RESOURCES);
430		}
431		rbuf_alloced = B_TRUE;
432	}
433
434	/* Copy the send buffers into the receive buffers */
435
436	/* Copy the MAD header */
437	bcopy((void *)msgimplp->im_msgbufs_send.im_bufs_mad_hdr,
438	    (void *)rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr,
439	    sizeof (ib_mad_hdr_t));
440
441	/*
442	 * Copy the management class header
443	 * For DR MADs, class header is of size 40 bytes and start
444	 * right after the MAD header.
445	 */
446	rmsgimplp->im_msgbufs_recv.im_bufs_cl_hdr =
447	    (uchar_t *)rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr +
448	    sizeof (ib_mad_hdr_t);
449	rmsgimplp->im_msgbufs_recv.im_bufs_cl_hdr_len =
450	    msgimplp->im_msgbufs_send.im_bufs_cl_hdr_len;
451	bcopy((void *)msgimplp->im_msgbufs_send.im_bufs_cl_hdr,
452	    (void *)rmsgimplp->im_msgbufs_recv.im_bufs_cl_hdr,
453	    msgimplp->im_msgbufs_send.im_bufs_cl_hdr_len);
454
455	/* Copy the management class data */
456	rmsgimplp->im_msgbufs_recv.im_bufs_cl_data =
457	    (uchar_t *)rmsgimplp->im_msgbufs_recv.im_bufs_mad_hdr +
458	    sizeof (ib_mad_hdr_t) +
459	    rmsgimplp->im_msgbufs_recv.im_bufs_cl_hdr_len;
460	rmsgimplp->im_msgbufs_recv.im_bufs_cl_data_len =
461	    msgimplp->im_msgbufs_send.im_bufs_cl_data_len;
462	bcopy((void *)msgimplp->im_msgbufs_send.im_bufs_cl_data,
463	    (void *)rmsgimplp->im_msgbufs_recv.im_bufs_cl_data,
464	    msgimplp->im_msgbufs_send.im_bufs_cl_data_len);
465
466	/* Copy the global address information from the source message */
467	bcopy((void *)&msgimplp->im_global_addr,
468	    (void *)&rmsgimplp->im_global_addr,
469	    sizeof (ibmf_global_addr_info_t));
470
471	/* Copy the local address information from the source message */
472	bcopy((void *)&msgimplp->im_local_addr,
473	    (void *)&rmsgimplp->im_local_addr,
474	    sizeof (ibmf_addr_info_t));
475
476	/*
477	 * Call the receive callback for the agent/manager the packet is
478	 * destined for.
479	 */
480	rmsgimplp->im_trans_state_flags |= IBMF_TRANS_STATE_FLAG_DONE;
481
482	/*
483	 * Decrement the message reference count
484	 * This count was incremented either when the message was found
485	 * on the client's message list (ibmf_i_find_msg()) or when
486	 * a new message was created for unsolicited data
487	 */
488	IBMF_MSG_DECR_REFCNT(rmsgimplp);
489
490	mutex_exit(&rmsgimplp->im_mutex);
491
492	if (rbuf_alloced) {
493		mutex_enter(&clientp->ic_kstat_mutex);
494		IBMF_ADD32_KSTATS(clientp, recv_bufs_alloced, 1);
495		mutex_exit(&clientp->ic_kstat_mutex);
496	}
497
498	/* add the source message to the source client's list */
499	ibmf_i_client_add_msg(clientp, msgimplp);
500
501	/* remove the destination message from the list */
502	ibmf_i_client_rem_msg(rclientp, rmsgimplp, &ref_cnt);
503
504	/*
505	 * Notify the client if the message reference count is zero.
506	 * At this point, we know that the transaction is done and
507	 * the message has been removed from the client's message list.
508	 * So, we only need to make sure the reference count is zero
509	 * before notifying the client.
510	 */
511	if (ref_cnt == 0)
512		ibmf_i_notify_client(rmsgimplp);
513
514	/* perform source client transaction termination processing */
515	ibmf_i_dr_loopback_term(clientp, msgimplp, blocking);
516
517	IBMF_TRACE_1(IBMF_TNF_DEBUG, DPRINT_L4, ibmf_i_dr_loopback_filter_end,
518	    IBMF_TNF_TRACE, "", "ibmf_i_dr_loopback_filter() exit, ret = %d\n",
519	    tnf_uint, status, ret);
520
521	return (IBMF_SUCCESS);
522}
523