/*
 * SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-only
 *
 * Copyright (c) 2018 Hewlett Packard Enterprise Development LP
 */

#include <stdio.h>
#include <stdlib.h>

#include <criterion/criterion.h>

#include "cxip.h"
#include "cxip_test_common.h"

#define RMA_WIN_KEY 0x1f

TestSuite(rma_no_init, .timeout = CXIT_DEFAULT_TIMEOUT);

Test(rma_no_init, xfer_disable_optimized_mrs_disable_prov_key_cache)
{
	int ret;
	bool value;
	uint64_t key;
	struct mem_region mem_window;
	size_t len = 16 * 1024;
	uint8_t *send_buf;
	struct fi_cq_tagged_entry cqe;
	struct cxip_mr_key mr_key;

	send_buf = calloc(1, len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	ret = setenv("CXIP_TEST_PROV_KEY", "1", 1);
	cr_assert_eq(ret, 0);

	cxit_setup_rma();

	value = false;
	ret = fi_control(&cxit_domain->fid,
			 FI_OPT_CXI_SET_OPTIMIZED_MRS, &value);
	cr_assert_eq(ret, FI_SUCCESS, "Unexpected call failure");

	value = false;
	ret = fi_control(&cxit_domain->fid,
			 FI_OPT_CXI_SET_PROV_KEY_CACHE, &value);
	cr_assert_eq(ret, FI_SUCCESS, "Unexpected call failure");

	ret = mr_create(len, FI_REMOTE_READ | FI_REMOTE_WRITE, 0, &key,
			&mem_window);
	cr_assert_eq(ret, FI_SUCCESS);

	mr_key.raw = key;
	cr_assert(mr_key.opt == 0);

	ret = fi_write(cxit_ep, send_buf, len, NULL, cxit_ep_fi_addr, 0, key,
		       NULL);
	cr_assert(ret == FI_SUCCESS);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	mr_destroy(&mem_window);
	cxit_teardown_rma();
	free(send_buf);
}

TestSuite(rma, .init = cxit_setup_rma, .fini = cxit_teardown_rma,
	  .timeout = CXIT_DEFAULT_TIMEOUT);

Test(rma, zero_byte_writev)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;

	mr_create(0, FI_REMOTE_WRITE | FI_REMOTE_READ, 0, &key_val,
		  &mem_window);

	ret = fi_writev(cxit_ep, NULL, NULL, 0, cxit_ep_fi_addr, 0, key_val,
			NULL);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writev failed: %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	mr_destroy(&mem_window);
}

Test(rma, zero_byte_writemsg)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct fi_rma_iov rma[1] = {};

	mr_create(0, FI_REMOTE_WRITE | FI_REMOTE_READ, 0, &key_val,
		  &mem_window);

	rma[0].key = key_val;

	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	ret = fi_writemsg(cxit_ep, &msg, 0);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed: %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	mr_destroy(&mem_window);
}

Test(rma, zero_byte_readv)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;

	mr_create(0, FI_REMOTE_WRITE | FI_REMOTE_READ, 0, &key_val,
		  &mem_window);

	ret = fi_readv(cxit_ep, NULL, NULL, 0, cxit_ep_fi_addr, 0, key_val,
		       NULL);
	cr_assert_eq(ret, FI_SUCCESS, "fi_readv failed: %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

	mr_destroy(&mem_window);
}

Test(rma, zero_byte_readmsg)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct fi_rma_iov rma[1] = {};

	mr_create(0, FI_REMOTE_WRITE | FI_REMOTE_READ, 0, &key_val,
		  &mem_window);

	rma[0].key = key_val;

	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	ret = fi_readmsg(cxit_ep, &msg, 0);
	cr_assert_eq(ret, FI_SUCCESS, "fi_readmsg failed: %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

	mr_destroy(&mem_window);
}

static void simple_write(void)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16 * 1024;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);
	}

	mr_destroy(&mem_window);
	free(send_buf);
}

/* Test fi_write simple case. Test IDC sizes to multi-packe sizes. */
Test(rma, simple_write)
{
	simple_write();
}

/* Test compatibility of client/provider keys */
Test(rma, key_compatibility)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16 * 1024;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fid_domain *domain2;
	struct fid_ep *ep2;
	struct fid_cq *tx_cq2;
	struct fid_cq *rx_cq2;
	struct fid_av *av2;
	struct cxip_addr ep2_addr;
	size_t addrlen = sizeof(ep2_addr);
	struct cxip_addr fake_addr = {.nic = 0xad, .pid = 0xbc};
	struct cxip_domain *dom;
	struct cxip_mr_key cxip_key;
	bool first_domain_prov_key;

	/* Create second RMA endpoint in the opposite client/provider
	 * mr_mode as the test default EP. When tested with
	 * CXIP_TEST_PROV_KEY=true is set, then the second EP is started
	 * in client key mode, if not set, then the second EP is started
	 * in provider key mode.
	 */
	if (cxit_fi->domain_attr->mr_mode & FI_MR_PROV_KEY) {
		first_domain_prov_key = true;
		cxit_fi->domain_attr->mr_mode &= ~FI_MR_PROV_KEY;
		cxit_fi->domain_attr->mr_key_size = sizeof(uint32_t);
	} else {
		first_domain_prov_key = false;
		cxit_fi->domain_attr->mr_mode |= FI_MR_PROV_KEY;
		cxit_fi->domain_attr->mr_key_size = sizeof(uint64_t);
	}
	ret = fi_domain(cxit_fabric, cxit_fi, &domain2, NULL);
	cr_assert(ret == FI_SUCCESS, "fi_domain 2nd domain");
	dom = container_of(domain2, struct cxip_domain,
			   util_domain.domain_fid);
	if (first_domain_prov_key)
		cr_assert(!dom->is_prov_key, "2nd domain not client key");
	else
		cr_assert(dom->is_prov_key, "2nd domain not provider key");

	ret = fi_endpoint(domain2, cxit_fi, &ep2, NULL);
	cr_assert(ret == FI_SUCCESS, "fi_endpoint 2nd endpoint");

	ret = fi_av_open(domain2, &cxit_av_attr, &av2, NULL);
	cr_assert(ret == FI_SUCCESS, "fi_av_open 2nd AV");
	ret = fi_ep_bind(ep2, &av2->fid, 0);
	cr_assert(ret == FI_SUCCESS, "fi_ep_bind 2nd AV");

	ret = fi_cq_open(domain2, &cxit_tx_cq_attr, &tx_cq2, NULL);
	cr_assert(ret == FI_SUCCESS, "fi_cq_open 2nd TX CQ");
	ret = fi_ep_bind(ep2, &tx_cq2->fid, FI_TRANSMIT);
	cr_assert(ret == FI_SUCCESS, "fi_ep_bind 2nd TX CQ");

	ret = fi_cq_open(domain2, &cxit_rx_cq_attr, &rx_cq2, NULL);
	cr_assert(ret == FI_SUCCESS, "fi_cq_open 2nd RX CQ");
	ret = fi_ep_bind(ep2, &rx_cq2->fid, FI_RECV);
	cr_assert(ret == FI_SUCCESS, "fi_ep_bind 2nd RX CQ");

	ret = fi_enable(ep2);
	cr_assert(ret == FI_SUCCESS, "fi_enable 2nd EP");

	ret = fi_getname(&ep2->fid, &ep2_addr, &addrlen);
	cr_assert(ret == FI_SUCCESS, "fi_getname 2nd EP");

	/* Setup AV, adding fake, first EP then second EP */
	ret = fi_av_insert(av2, (void *)&fake_addr, 1, NULL, 0, NULL);
	cr_assert(ret == 1);
	ret = fi_av_insert(av2, (void *)&cxit_ep_addr, 1, NULL, 0, NULL);
	cr_assert(ret == 1, "fi_av_insert 1st EP into AV2");
	ret = fi_av_insert(av2, (void *)&ep2_addr, 1, NULL, 0, NULL);
	cr_assert(ret == 1, "fi_av_insert 2nd EP into AV2");

	/* Add second EP to default EP's AV */
	ret = fi_av_insert(cxit_av, (void *)&ep2_addr, 1, NULL, 0, NULL);
	cr_assert(ret == 1, "fi_av_insert 2nd EP into cxit_av");

	/* First EP creates a MR with a key of the type specified in
	 * the associated domain. The second EP will use this key
	 * which to initiate a transfer.
	 */
	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	cxip_key.raw = key_val;
	if (first_domain_prov_key)
		cr_assert(cxip_key.is_prov, "Key is not provider key");
	else
		cr_assert(!cxip_key.is_prov, "Key is not client key");

	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		ret = fi_write(ep2, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(tx_cq2, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);
	}

	ret = fi_close(&ep2->fid);
	cr_assert(ret == FI_SUCCESS, "fi_close EP2");
	ret = fi_close(&tx_cq2->fid);
	cr_assert(ret == FI_SUCCESS, "fi_close TX CQ2");
	ret = fi_close(&rx_cq2->fid);
	cr_assert(ret == FI_SUCCESS, "fi_close RX CQ2");
	ret = fi_close(&av2->fid);
	cr_assert(ret == FI_SUCCESS, "fi_close AV2");
	ret = fi_close(&domain2->fid);
	cr_assert(ret == FI_SUCCESS, "fi_close domain2");

	mr_destroy(&mem_window);
	free(send_buf);
}

void cxit_setup_rma_opt(void)
{
	cxit_setup_getinfo();

	/* Explicitly request unordered RMA */
	cxit_fi_hints->caps = FI_RMA;
	cxit_fi_hints->tx_attr->msg_order = 0;

	cxit_setup_rma();
}

TestSuite(rma_opt, .init = cxit_setup_rma_opt, .fini = cxit_teardown_rma,
	  .timeout = CXIT_DEFAULT_TIMEOUT);

/* Test an optimal fi_write. */
Test(rma_opt, opt_write)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16 * 1024;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	uint64_t res_start;
	uint64_t res_end;
	uint64_t hits_start;
	uint64_t hits_end;
	struct cxip_ep *cxi_ep;

	ret = cxit_dom_read_cntr(C_CNTR_IXE_RX_PTL_RESTRICTED_PKT,
				 &res_start, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	ret = cxit_dom_read_cntr(C_CNTR_LPE_PLEC_HITS,
				 &hits_start, NULL, false);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create_ext(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, NULL,
		      &mem_window);

	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS, "ret is: %d\n", ret);

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);
	}

	mr_destroy(&mem_window);
	free(send_buf);

	ret = cxit_dom_read_cntr(C_CNTR_IXE_RX_PTL_RESTRICTED_PKT,
				 &res_end, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);
	cr_expect(res_end > res_start);

	ret = cxit_dom_read_cntr(C_CNTR_LPE_PLEC_HITS,
				 &hits_end, NULL, false);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	cxi_ep = container_of(cxit_ep, struct cxip_ep, ep);
	if (!is_netsim(cxi_ep->ep_obj)) {
		cr_assert(hits_end > hits_start);
	} else {
		if (hits_end == hits_start)
			printf("PLEC Hits not registered (unsupported on netsim)\n");
	}
}

/* Test simple writes to a standard MR. */
Test(rma, simple_write_std_mr)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16 * 1024;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = 0xdef;
	struct fi_cq_tagged_entry cqe;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);
	}

	mr_destroy(&mem_window);
	free(send_buf);
}

/* Test fi_writev simple case */
Test(rma, simple_writev)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct iovec iov[1];

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	/* Send 8 bytes from send buffer data to RMA window 0 */
	ret = fi_writev(cxit_ep, iov, NULL, 1, cxit_ep_fi_addr, 0, key_val,
			NULL);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writev failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);
}

void do_writemsg(uint64_t flags)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Send 8 bytes from send buffer data to RMA window 0 at FI address 0
	 * (self)
	 */
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	if (flags & FI_CXI_HRP)
		usleep(1000);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);
}

/* Test fi_writemsg with flags */
Test(rma, writemsg)
{
	do_writemsg(0);
	do_writemsg(FI_FENCE);
}

void cxit_rma_setup_nofence(void)
{
	cxit_setup_getinfo();
	cxit_fi_hints->caps = CXIP_EP_PRI_CAPS;
	cxit_setup_rma();
}

/* Test RMA without FI_FENCE */
Test(rma_nofence, nofence,
     .init = cxit_rma_setup_nofence,
     .fini = cxit_teardown_rma)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	ret = fi_writemsg(cxit_ep, &msg, FI_FENCE);
	cr_assert(ret == -FI_EINVAL);

	ret = fi_readmsg(cxit_ep, &msg, FI_FENCE);
	cr_assert(ret == -FI_EINVAL);

	mr_destroy(&mem_window);
	free(send_buf);
}

void cxit_rma_setup_no_rma_events(void)
{
	cxit_setup_getinfo();

	cxit_fi_hints->caps = FI_RMA | FI_ATOMIC;
	cxit_setup_rma();
}

/* Test HRP Put */
Test(rma_opt_hrp, hrp,
     .init = cxit_rma_setup_no_rma_events,
     .fini = cxit_teardown_rma,
     .timeout = CXIT_DEFAULT_TIMEOUT)
{
	int ret;
	uint64_t hrp_acks_start;
	uint64_t hrp_acks_end;
	struct cxip_ep *cxi_ep;

	/* HRP not supported in netsim */
	cxi_ep = container_of(cxit_ep, struct cxip_ep, ep);
	if (is_netsim(cxi_ep->ep_obj))
		return;

	ret = cxit_dom_read_cntr(C_CNTR_HNI_HRP_ACK,
				 &hrp_acks_start, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	do_writemsg(0);
	do_writemsg(FI_CXI_HRP);
	do_writemsg(0);

	for (int i = 0; i < 10; i++)
		do_writemsg(FI_CXI_HRP);

	ret = cxit_dom_read_cntr(C_CNTR_HNI_HRP_ACK,
				 &hrp_acks_end, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	cr_assert_eq(hrp_acks_end - hrp_acks_start, 11,
		     "unexpected hrp_acks count: %lu\n",
		     hrp_acks_end - hrp_acks_start);
}

/* Perform a write that uses a flushing ZBR at the target. */
Test(rma, flush)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	uint64_t flags = FI_DELIVERY_COMPLETE;
	uint64_t flushes_start;
	uint64_t flushes_end;

	ret = cxit_dom_read_cntr(C_CNTR_IXE_DMAWR_FLUSH_REQS,
				 &flushes_start, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Send 8 bytes from send buffer data to RMA window 0 at FI address 0
	 * (self)
	 */
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);

	ret = cxit_dom_read_cntr(C_CNTR_IXE_DMAWR_FLUSH_REQS,
				 &flushes_end, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);
	cr_assert(flushes_end > flushes_start);
}

/* Test fi_writemsg with FI_INJECT flag */
Test(rma, simple_writemsg_inject)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	uint64_t flags = FI_INJECT;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Send 8 bytes from send buffer data to RMA window 0 at FI address 0
	 * (self)
	 */
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);

	/* Try using standard MR */

	key_val = 1000;
	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);
	rma[0].key = key_val;

	/* Send 8 bytes from send buffer data to RMA window 0 at FI address 0
	 * (self)
	 */
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);

	free(send_buf);
}

/* Test fi_inject_write simple case */
Test(rma, simple_inject_write)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	cr_assert(!fi_cntr_read(cxit_write_cntr));

	/* Test invalid inject length */
	ret = fi_inject_write(cxit_ep, send_buf,
			      cxit_fi->tx_attr->inject_size + 100,
			      cxit_ep_fi_addr, 0, key_val);
	cr_assert(ret == -FI_EMSGSIZE);

	/* Send 8 bytes from send buffer data to RMA window 0 */
	ret = fi_inject_write(cxit_ep, send_buf, send_len, cxit_ep_fi_addr, 0,
			      key_val);
	cr_assert(ret == FI_SUCCESS);

	while (fi_cntr_read(cxit_write_cntr) != 1)
		;

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	/* Make sure an event wasn't delivered */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	mr_destroy(&mem_window);
	free(send_buf);
}

static void simple_read(void)
{
	int ret;
	uint8_t *local;
	int remote_len = 0x1000;
	int local_len = 8;
	uint64_t key_val = 0xa;
	struct fi_cq_tagged_entry cqe;
	struct mem_region remote;

	local = calloc(1, local_len);
	cr_assert_not_null(local, "local alloc failed");

	mr_create(remote_len, FI_REMOTE_READ, 0xc0, &key_val, &remote);

	cr_assert(!fi_cntr_read(cxit_read_cntr));

	/* Get 8 bytes from the source buffer to the receive buffer */
	ret = fi_read(cxit_ep, local, local_len, NULL, cxit_ep_fi_addr, 0,
		      key_val, NULL);
	cr_assert_eq(ret, FI_SUCCESS, "fi_read() failed (%d)", ret);

	while (fi_cntr_read(cxit_read_cntr) != 1)
		;

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read() failed (%d)", ret);

	validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

	/* Validate sent data */
	for (int i = 0; i < local_len; i++)
		cr_expect_eq(local[i], remote.mem[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     local[i], remote.mem[i]);

	mr_destroy(&remote);
	free(local);
}

/* Test fi_read simple case */
Test(rma, simple_read)
{
	simple_read();
}

/* Test fi_readv simple case */
Test(rma, simple_readv)
{
	int ret;
	uint8_t *local;
	int remote_len = 0x1000;
	int local_len = 8;
	uint64_t key_val = 0x2a;
	struct fi_cq_tagged_entry cqe;
	struct mem_region remote;
	struct iovec iov[1];

	local = calloc(1, local_len);
	cr_assert_not_null(local, "local alloc failed");

	mr_create(remote_len, FI_REMOTE_READ, 0x3c, &key_val, &remote);

	iov[0].iov_base = local;
	iov[0].iov_len = local_len;

	/* Get 8 bytes from the source buffer to the receive buffer */
	ret = fi_readv(cxit_ep, iov, NULL, 1, cxit_ep_fi_addr, 0, key_val,
		       NULL);
	cr_assert_eq(ret, FI_SUCCESS, "fi_readv() failed (%d)", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read() failed (%d)", ret);

	validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

	/* Validate sent data */
	for (int i = 0; i < local_len; i++)
		cr_expect_eq(local[i], remote.mem[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     local[i], remote.mem[i]);

	mr_destroy(&remote);
	free(local);
}

/* Test fi_readmsg simple case */
Test(rma, simple_readmsg)
{
	int ret;
	uint8_t *local;
	int remote_len = 0x1000;
	int local_len = 8;
	uint64_t key_val = 0x2a;
	struct fi_cq_tagged_entry cqe;
	struct mem_region remote;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	uint64_t flags = 0;

	local = calloc(1, local_len);
	cr_assert_not_null(local, "local alloc failed");

	mr_create(remote_len, FI_REMOTE_READ, 0xd9, &key_val, &remote);

	iov[0].iov_base = local;
	iov[0].iov_len = local_len;

	rma[0].addr = 0;
	rma[0].len = local_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Get 8 bytes from the source buffer to the receive buffer */
	ret = fi_readmsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_readv() failed (%d)", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read() failed (%d)", ret);

	validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

	/* Validate sent data */
	for (int i = 0; i < local_len; i++)
		cr_expect_eq(local[i], remote.mem[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     local[i], remote.mem[i]);

	mr_destroy(&remote);
	free(local);
}

/* Test fi_readmsg failure cases */
Test(rma, readmsg_failures)
{
	int ret;
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	struct fi_msg_rma msg = {
		.msg_iov = iov,
		.rma_iov = rma,
		.iov_count = 1,
		.rma_iov_count = 1,
	};
	uint64_t flags = 0;

	/* Invalid msg value */
	ret = fi_readmsg(cxit_ep, NULL, flags);
	cr_assert_eq(ret, -FI_EINVAL, "NULL msg return %d", ret);

	msg.iov_count = cxit_fi->tx_attr->rma_iov_limit + 1;
	ret = fi_readmsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, -FI_EINVAL, "Invalid iov_count return %d", ret);

	msg.iov_count = cxit_fi->tx_attr->rma_iov_limit;
	flags = FI_DIRECTED_RECV; /* Invalid flag value */
	ret = fi_readmsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, -FI_EBADFLAGS, "Invalid flag unexpected return %d",
		     ret);
}

/* Test fi_writemsg failure cases */
Test(rma, writemsg_failures)
{
	int ret;
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	struct fi_msg_rma msg = {
		.msg_iov = iov,
		.rma_iov = rma,
		.iov_count = 1,
		.rma_iov_count = 1,
	};
	uint64_t flags = 0;
	size_t send_len = 10;
	char send_buf[send_len];

	/* Invalid msg value */
	ret = fi_writemsg(cxit_ep, NULL, flags);
	cr_assert_eq(ret, -FI_EINVAL, "NULL msg return %d", ret);

	msg.iov_count = cxit_fi->tx_attr->rma_iov_limit + 1;
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, -FI_EINVAL, "Invalid iov_count return %d", ret);

	msg.iov_count = cxit_fi->tx_attr->rma_iov_limit;
	flags = FI_DIRECTED_RECV; /* Invalid flag value */
	ret = fi_writemsg(cxit_ep, &msg, flags);
	cr_assert_eq(ret, -FI_EBADFLAGS, "Invalid flag return %d", ret);

	/* Invalid length */
	iov[0].iov_base = send_buf;
	iov[0].iov_len = cxit_fi->ep_attr->max_msg_size + 1;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = 0xa;
	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;

	ret = fi_writemsg(cxit_ep, &msg, 0);
	cr_assert_eq(ret, -FI_EMSGSIZE, "Invalid flag return %d", ret);

	/* Invalid inject length */
	iov[0].iov_len = C_MAX_IDC_PAYLOAD_RES+1;

	ret = fi_writemsg(cxit_ep, &msg, FI_INJECT);
	cr_assert_eq(ret, -FI_EMSGSIZE, "Invalid flag return %d", ret);
}

void rmamsg_bounds(bool write, bool opt_mr)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = opt_mr ? RMA_WIN_KEY : 200;
	struct fi_cq_tagged_entry cqe;
	struct fi_cq_err_entry err;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	size_t good_len = 4096;
	char *src_buf;

	/* Create over-sized send buffer for bounds checking */
	src_buf = calloc(1, good_len * 2);
	cr_assert_not_null(src_buf, "send_buf alloc failed");
	mr_create(good_len,
		  write ? FI_REMOTE_WRITE : FI_REMOTE_READ, 0xa0,
		  &key_val, &mem_window);
	memset(mem_window.mem, 0x33, good_len);

	/* Good length to verify operation */
	iov[0].iov_base = src_buf;
	iov[0].iov_len = good_len;

	rma[0].addr = 0;
	rma[0].len = good_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;
	if (write)
		ret = fi_writemsg(cxit_ep, &msg, FI_COMPLETION);
	else
		ret = fi_readmsg(cxit_ep, &msg, FI_COMPLETION);

	cr_assert_eq(ret, FI_SUCCESS, "Bad RMA API status %d", ret);
	do {
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	} while (ret == -FI_EAGAIN);
	cr_assert_eq(ret, 1, "Unexpected RMA failure");

	/* Use a bad length to cause a bounds violation and
	 * verify failure is detected.
	 */
	iov[0].iov_len = good_len * 2;
	rma[0].len = good_len * 2;

	if (write)
		ret = fi_writemsg(cxit_ep, &msg, FI_COMPLETION);
	else
		ret = fi_readmsg(cxit_ep, &msg, FI_COMPLETION);

	cr_assert_eq(ret, FI_SUCCESS, "Bad RMA return status %d", ret);

	/* There should be a source error entry. */
	do {
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	} while (ret == -FI_EAGAIN);
	cr_assert_eq(ret, -FI_EAVAIL, "Unexpected RMA success");

	ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
	cr_assert(ret == 1);
	cr_assert_eq(err.err, FI_EIO, "Error return %d", err.err);

	mr_destroy(&mem_window);
	free(src_buf);
}

Test(rma, writemsg_bounds_opt)
{
	rmamsg_bounds(true, true);
}

Test(rma, writemsg_bounds_std)
{
	rmamsg_bounds(true, false);
}

Test(rma, readmsg_bounds_opt)
{
	rmamsg_bounds(false, true);
}

Test(rma, readmsg_bounds_std)
{
	rmamsg_bounds(false, false);
}

/* Test fi_readv failure cases */
Test(rma, readv_failures)
{
	int ret;
	struct iovec iov = {};

	 /* Invalid count value */
	ret = fi_readv(cxit_ep, &iov, NULL,
		       cxit_fi->tx_attr->rma_iov_limit + 1,
		       cxit_ep_fi_addr, 0, 0, NULL);
	cr_assert_eq(ret, -FI_EINVAL, "Invalid count return %d", ret);
}

/* Test fi_writev failure cases */
Test(rma, writev_failures)
{
	int ret;
	struct iovec iov = {};

	 /* Invalid count value */
	ret = fi_writev(cxit_ep, &iov, NULL,
			cxit_fi->tx_attr->rma_iov_limit + 1,
			cxit_ep_fi_addr, 0, 0, NULL);
	cr_assert_eq(ret, -FI_EINVAL, "Invalid count return %d", ret);
}

/* Perform an RMA write spanning a page */
Test(rma, write_spanning_page)
{
	int ret;
	uint8_t *send_buf;
	uint8_t *send_addr;
	int win_len = s_page_size * 2;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	send_addr = (uint8_t *)FLOOR(send_buf + s_page_size, s_page_size) - 4;
	memset(send_addr, 0xcc, send_len);

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);
	memset(mem_window.mem, 0x33, win_len);

	/* Send 8 bytes from send buffer data to RMA window 0 */
	ret = fi_write(cxit_ep, send_addr, send_len, NULL, cxit_ep_fi_addr, 0,
		       key_val, NULL);
	cr_assert(ret == FI_SUCCESS);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_addr[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_addr[i]);

	mr_destroy(&mem_window);
	free(send_buf);
}

Test(rma, rma_cleanup)
{
	int ret;
	long i;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	int writes = 50;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	for (i = 0; i < win_len; i++)
		send_buf[i] = 0xb1 * i;

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	/* Send 8 bytes from send buffer data to RMA window 0 */
	for (i = 0; i < writes; i++) {
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
				cxit_ep_fi_addr, 0, key_val, (void *)i);
		cr_assert(ret == FI_SUCCESS);
	}

	mr_destroy(&mem_window);

	/* Exit without gathering events. */
}

void cxit_setup_rma_selective_completion(void)
{
	cxit_tx_cq_bind_flags |= FI_SELECTIVE_COMPLETION;

	cxit_setup_getinfo();
	cxit_fi_hints->tx_attr->op_flags = FI_COMPLETION;
	cxit_setup_rma();
}

/* Test selective completion behavior with RMA. */
Test(rma_sel, selective_completion,
     .init = cxit_setup_rma_selective_completion,
     .fini = cxit_teardown_rma)
{
	int ret;
	uint8_t *loc_buf;
	int win_len = 0x1000;
	int loc_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov;
	struct fi_rma_iov rma;
	int count = 0;

	loc_buf = calloc(1, win_len);
	cr_assert_not_null(loc_buf, "loc_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE | FI_REMOTE_READ, 0xa0, &key_val,
		  &mem_window);

	iov.iov_base = loc_buf;
	iov.iov_len = loc_len;

	rma.addr = 0;
	rma.key = key_val;

	msg.msg_iov = &iov;
	msg.iov_count = 1;
	msg.rma_iov = &rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Puts */

	/* Completion requested by default. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		ret = fi_write(cxit_ep, loc_buf, loc_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);
		count++;

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			cr_assert_eq(mem_window.mem[i], loc_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], loc_buf[i]);
	}

	/* Completion explicitly requested. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		iov.iov_len = loc_len;
		ret = fi_writemsg(cxit_ep, &msg, FI_COMPLETION);
		cr_assert(ret == FI_SUCCESS);
		count++;

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			cr_assert_eq(mem_window.mem[i], loc_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], loc_buf[i]);
	}

	/* Suppress completion. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		iov.iov_len = loc_len;
		ret = fi_writemsg(cxit_ep, &msg, 0);
		cr_assert(ret == FI_SUCCESS);
		count++;

		while (fi_cntr_read(cxit_write_cntr) != count)
			;

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			while (mem_window.mem[i] != loc_buf[i])
				sched_yield();

		/* Ensure no events were generated */
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
		cr_assert(ret == -FI_EAGAIN);
	}

	/* Inject never generates an event */
	loc_len = 8;
	ret = fi_inject_write(cxit_ep, loc_buf, loc_len, cxit_ep_fi_addr, 0,
			      key_val);
	cr_assert(ret == FI_SUCCESS);

	/* Validate sent data */
	for (int i = 0; i < loc_len; i++)
		while (mem_window.mem[i] != loc_buf[i])
			sched_yield();

	/* Make sure an event wasn't delivered */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	/* Gets */
	memset(loc_buf, 0, win_len);
	count = 0;

	/* Completion requested by default. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		memset(loc_buf, 0, loc_len);
		ret = fi_read(cxit_ep, loc_buf, loc_len, NULL,
			      cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);
		count++;

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			cr_assert_eq(mem_window.mem[i], loc_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], loc_buf[i]);
	}

	/* Completion explicitly requested. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		memset(loc_buf, 0, loc_len);
		iov.iov_len = loc_len;
		ret = fi_readmsg(cxit_ep, &msg, FI_COMPLETION);
		cr_assert(ret == FI_SUCCESS);
		count++;

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_READ, NULL);

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			cr_assert_eq(mem_window.mem[i], loc_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], loc_buf[i]);
	}

	/* Suppress completion. */
	for (loc_len = 1; loc_len <= win_len; loc_len <<= 1) {
		memset(loc_buf, 0, loc_len);
		iov.iov_len = loc_len;
		ret = fi_readmsg(cxit_ep, &msg, 0);
		cr_assert(ret == FI_SUCCESS);
		count++;

		while (fi_cntr_read(cxit_read_cntr) != count)
			;

		/* Validate sent data */
		for (int i = 0; i < loc_len; i++)
			cr_assert_eq(mem_window.mem[i], loc_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], loc_buf[i]);

		/* Ensure no events were generated */
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
		cr_assert(ret == -FI_EAGAIN);
	}

	mr_destroy(&mem_window);
	free(loc_buf);
}

void cxit_setup_rma_selective_completion_suppress(void)
{
	cxit_tx_cq_bind_flags |= FI_SELECTIVE_COMPLETION;

	cxit_setup_getinfo();
	cxit_fi_hints->tx_attr->op_flags = 0;
	cxit_setup_rma();
}

/* Test selective completion behavior with RMA. */
Test(rma_sel, selective_completion_suppress,
     .init = cxit_setup_rma_selective_completion_suppress,
     .fini = cxit_teardown_rma)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov;
	struct fi_rma_iov rma;
	int write_count = 0;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	iov.iov_base = send_buf;
	iov.iov_len = send_len;

	rma.addr = 0;
	rma.key = key_val;

	msg.msg_iov = &iov;
	msg.iov_count = 1;
	msg.rma_iov = &rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Normal writes do not generate completions */
	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		memset(mem_window.mem, 0, send_len);
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);
		write_count++;

		while (fi_cntr_read(cxit_write_cntr) != write_count)
			;

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			while (mem_window.mem[i] != send_buf[i])
				sched_yield();

		/* Ensure no events were generated */
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
		cr_assert(ret == -FI_EAGAIN);
	}

	/* Request completions from fi_writemsg */
	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		memset(mem_window.mem, 0, send_len);
		iov.iov_len = send_len;
		ret = fi_writemsg(cxit_ep, &msg, FI_COMPLETION);
		cr_assert(ret == FI_SUCCESS);
		write_count++;

		/* Wait for async event indicating data has been sent */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);
	}

	/* Suppress completions using fi_writemsg */
	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		memset(mem_window.mem, 0, send_len);
		iov.iov_len = send_len;
		ret = fi_writemsg(cxit_ep, &msg, 0);
		cr_assert(ret == FI_SUCCESS);
		write_count++;

		while (fi_cntr_read(cxit_write_cntr) != write_count)
			;

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			while (mem_window.mem[i] != send_buf[i])
				sched_yield();

		/* Ensure no events were generated */
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
		cr_assert(ret == -FI_EAGAIN);
	}

	/* Inject never generates an event */
	send_len = 8;
	memset(mem_window.mem, 0, send_len);
	ret = fi_inject_write(cxit_ep, send_buf, send_len, cxit_ep_fi_addr, 0,
			      key_val);
	cr_assert(ret == FI_SUCCESS);
	write_count++;

	while (fi_cntr_read(cxit_write_cntr) != write_count)
		;

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		while (mem_window.mem[i] != send_buf[i])
			sched_yield();

	/* Make sure an event wasn't delivered */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	mr_destroy(&mem_window);
	free(send_buf);
}

Test(rma_sel, fi_more_write_stream_optimized,
     .init = cxit_setup_rma_selective_completion_suppress,
     .fini = cxit_teardown_rma)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = 0x0;
	struct fi_msg_rma msg = {};
	struct fi_rma_iov rma = {};
	unsigned int write_count = 0;
	struct fid_cntr *cntr = cxit_write_cntr;

	mr_create(0, FI_REMOTE_WRITE, 0, &key_val, &mem_window);

	rma.key = key_val;
	msg.rma_iov = &rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	do {
		ret = fi_writemsg(cxit_ep, &msg, FI_MORE);
		cr_assert((ret == FI_SUCCESS) || (ret == -FI_EAGAIN));
		if (ret == FI_SUCCESS)
			write_count++;
	} while (ret != -FI_EAGAIN);

	cr_assert(write_count >= cxit_fi_hints->tx_attr->size);

	do {
		ret = fi_writemsg(cxit_ep, &msg, FI_MORE);
	} while (ret == -FI_EAGAIN);
	cr_assert(ret == FI_SUCCESS);
	write_count++;

	ret = fi_writemsg(cxit_ep, &msg, 0);
	cr_assert(ret == FI_SUCCESS);
	write_count++;

	ret = fi_cntr_wait(cntr, write_count, 10000);
	cr_assert(ret == FI_SUCCESS, "ret=%d", ret);

	mr_destroy(&mem_window);
}

Test(rma_sel, fi_more_write_stream_mix_optimzied_unoptimized,
     .init = cxit_setup_rma_selective_completion_suppress,
     .fini = cxit_teardown_rma)
{
	int ret;
	struct mem_region opt_mem_window;
	struct mem_region mem_window;
	uint64_t opt_key_val = 0x0;
	uint64_t key_val = 0x1234;
	struct fi_msg_rma msg = {};
	struct fi_rma_iov rma = {};
	unsigned int write_count = 0;
	struct fid_cntr *cntr = cxit_write_cntr;

	mr_create(0, FI_REMOTE_WRITE, 0, &opt_key_val, &opt_mem_window);
	mr_create(0, FI_REMOTE_WRITE, 0, &key_val, &mem_window);

	rma.key = opt_key_val;
	msg.rma_iov = &rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	do {
		ret = fi_writemsg(cxit_ep, &msg, FI_MORE);
		cr_assert((ret == FI_SUCCESS) || (ret == -FI_EAGAIN));
		if (ret == FI_SUCCESS)
			write_count++;
	} while (ret != -FI_EAGAIN);

	cr_assert(write_count >= cxit_fi_hints->tx_attr->size);

	rma.key = key_val;
	do {
		ret = fi_writemsg(cxit_ep, &msg, FI_MORE);
	} while (ret == -FI_EAGAIN);
	cr_assert(ret == FI_SUCCESS);
	write_count++;

	ret = fi_writemsg(cxit_ep, &msg, 0);
	cr_assert(ret == FI_SUCCESS, "ret=%d", ret);
	write_count++;

	ret = fi_cntr_wait(cntr, write_count, 10000);
	cr_assert(ret == FI_SUCCESS, "ret=%d", ret);

	mr_destroy(&mem_window);
	mr_destroy(&opt_mem_window);
}

Test(rma_sel, fi_more_read_stream,
     .init = cxit_setup_rma_selective_completion_suppress,
     .fini = cxit_teardown_rma)
{
	int ret;
	struct mem_region mem_window;
	uint64_t key_val = 0x0;
	struct fi_msg_rma msg = {};
	struct fi_rma_iov rma = {};
	unsigned int count = 0;
	struct fid_cntr *cntr = cxit_read_cntr;

	mr_create(0, FI_REMOTE_READ, 0, &key_val, &mem_window);

	rma.key = key_val;
	msg.rma_iov = &rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	do {
		ret = fi_readmsg(cxit_ep, &msg, FI_MORE);
		cr_assert((ret == FI_SUCCESS) || (ret == -FI_EAGAIN));
		if (ret == FI_SUCCESS)
			count++;
	} while (ret != -FI_EAGAIN);

	cr_assert(count >= cxit_fi_hints->tx_attr->size);

	do {
		ret = fi_readmsg(cxit_ep, &msg, FI_MORE);
	} while (ret == -FI_EAGAIN);
	cr_assert(ret == FI_SUCCESS);
	count++;

	ret = fi_readmsg(cxit_ep, &msg, 0);
	cr_assert(ret == FI_SUCCESS);
	count++;

	ret = fi_cntr_wait(cntr, count, 10000);
	cr_assert(ret == FI_SUCCESS, "ret=%d", ret);

	mr_destroy(&mem_window);
}

/* Test remote counter events with RMA */
Test(rma, rem_cntr)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16 * 1024;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	int count = 0;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	for (send_len = 1; send_len <= win_len; send_len <<= 1) {
		ret = fi_write(cxit_ep, send_buf, send_len, NULL,
			       cxit_ep_fi_addr, 0, key_val, NULL);
		cr_assert(ret == FI_SUCCESS);

		/* Wait for remote counter event, then check data */
		count++;

		while (fi_cntr_read(cxit_rem_cntr) != count)
			;

		/* Validate sent data */
		for (int i = 0; i < send_len; i++)
			cr_assert_eq(mem_window.mem[i], send_buf[i],
				     "data mismatch, element: (%d) %02x != %02x\n", i,
				     mem_window.mem[i], send_buf[i]);

		/* Gather source completion after data */
		ret = cxit_await_completion(cxit_tx_cq, &cqe);
		cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

		validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);
	}

	mr_destroy(&mem_window);
	free(send_buf);
}

/* Test RMA FI_MORE */
Test(rma, more)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 16;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	int i;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");
	for (i = 0; i < win_len; i++)
		send_buf[i] = 0xa + i;

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	ret = fi_writemsg(cxit_ep, &msg, FI_MORE);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Ensure no completion before the doorbell ring */
	do {
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
		cr_assert_eq(ret, -FI_EAGAIN,
			     "write failed %d", ret);
	} while (i++ < 100000);

	iov[0].iov_base = send_buf + send_len;
	rma[0].addr += send_len;
	ret = fi_writemsg(cxit_ep, &msg, 0);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for two events. */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);
}

Test(rma, std_mr_inject)
{
	int ret;
	uint8_t *send_buf;
	int iters = 10;
	int send_len = 8;
	int win_len = send_len * iters;
	struct mem_region mem_window;
	uint64_t key_val = CXIP_PTL_IDX_MR_OPT_CNT;
	struct fi_cq_tagged_entry cqe;
	int i;

	send_buf = calloc(1, send_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0xa0, &key_val, &mem_window);

	cr_assert(!fi_cntr_read(cxit_write_cntr));

	for (i = 0; i < iters; i++) {
		/* Send 8 bytes from send buffer data to RMA window 0 */
		ret = fi_inject_write(cxit_ep, send_buf, send_len,
				      cxit_ep_fi_addr, i * send_len, key_val);
		cr_assert(ret == FI_SUCCESS);
	}

	/* Corrupt the user buffer to make sure the NIC is not using it for an
	 * inject.
	 */
	memset(send_buf, 0xff, send_len);

	while (fi_cntr_read(cxit_write_cntr) != iters)
		;

	/* Validate sent data */
	for (int i = 0; i < win_len; i++)
		cr_assert_eq(mem_window.mem[i], 0,
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	/* Make sure an event wasn't delivered */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	mr_destroy(&mem_window);
	free(send_buf);
}

static void rma_invalid_target_mr_key(uint64_t rkey)
{
	int ret;
	struct fi_cq_tagged_entry cqe;
	struct fi_cq_err_entry err;

	/* Zero byte write to invalid MR key. */
	ret = fi_inject_write(cxit_ep, NULL, 0, cxit_ep_fi_addr, 0, rkey);
	cr_assert(ret == FI_SUCCESS);

	while (fi_cntr_readerr(cxit_write_cntr) != 1)
		;

	/* No target event should be generated. */
	ret = fi_cq_read(cxit_rx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	/* There should be an source error entry. */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAVAIL);

	/* Expect a source error. */
	ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
	cr_assert(ret == 1);

	/* Expect no other events. */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);
}

Test(rma, invalid_target_std_mr_key)
{
	rma_invalid_target_mr_key(0x1234);
}

Test(rma, invalid_target_opt_mr_key)
{
	rma_invalid_target_mr_key(0x10);
}

Test(rma, invalid_source_mr_key)
{
	int ret;

	ret = fi_inject_write(cxit_ep, NULL, 0, cxit_ep_fi_addr, 0,
			      0x100000001);
	cr_assert(ret == -FI_EKEYREJECTED);
}

static void rma_invalid_read_target_mr_key(uint64_t rkey)
{
	int ret;
	struct fi_cq_tagged_entry cqe;
	struct fi_cq_err_entry err;

	/* Zero byte read to invalid MR key. */
	ret = fi_read(cxit_ep, NULL, 0, NULL, cxit_ep_fi_addr, 0, rkey, NULL);
	cr_assert(ret == FI_SUCCESS);

	while (fi_cntr_readerr(cxit_read_cntr) != 1)
		;

	/* No target event should be generated. */
	ret = fi_cq_read(cxit_rx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	/* There should be an source error entry. */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAVAIL);

	/* Expect a source error. */
	ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
	cr_assert(ret == 1);

	/* Expect no other events. */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);
}

Test(rma, invalid_read_target_std_mr_key)
{
	rma_invalid_read_target_mr_key(0x1234);
}

Test(rma, invalid_read_target_opt_mr_key)
{
	rma_invalid_read_target_mr_key(0x10);
}

/* Tests to verify FI_RM_ENABLED */

static void mr_overrun(bool write, bool use_cq)
{
     int ret;
     uint8_t *local;
     size_t good_len = 4096;
     uint64_t key_val = 0xa;
     struct fi_cq_err_entry err;
     struct fi_cq_tagged_entry cqe;
     struct mem_region remote;

     /* Create over-sized local buffer */
     local = calloc(1, good_len * 2);
     cr_assert_not_null(local, "local alloc failed");

     mr_create(good_len, write ? FI_REMOTE_WRITE : FI_REMOTE_READ, 0xc0,
                    &key_val, &remote);

     /* Perform good length data transfer first */
     if(write) {
          ret = fi_write(cxit_ep, local, good_len, NULL, cxit_ep_fi_addr, 0,
                    key_val, NULL);
          cr_assert_eq(ret, FI_SUCCESS, "fi_write() failed (%d)", ret);
     }
     else {
          ret = fi_read(cxit_ep, local, good_len, NULL, cxit_ep_fi_addr, 0,
                    key_val, NULL);
          cr_assert_eq(ret, FI_SUCCESS, "fi_read() failed (%d)", ret);
     }

     if (use_cq) {
          /* Wait for async event indicating data has been sent */
          ret = cxit_await_completion(cxit_tx_cq, &cqe);
          cr_assert_eq(ret, 1, "fi_cq_read() failed (%d)", ret);

          validate_tx_event(&cqe, FI_RMA | (write ? FI_WRITE : FI_READ), NULL);
     } else {
          while (fi_cntr_read(write ? cxit_write_cntr : cxit_read_cntr) != 1)
               ;
     }

     /* Validate read data */
     for (int i = 0; i < good_len; i++)
          cr_expect_eq(local[i], remote.mem[i],
                    "data mismatch, element: (%d) %02x != %02x\n", i,
                    local[i], remote.mem[i]);

     /* Perform overrun data transfer */
     if (write) {
          ret = fi_write(cxit_ep, local, good_len*2, NULL, cxit_ep_fi_addr,
                    0, key_val, NULL);
          cr_assert_eq(ret, FI_SUCCESS, "fi_write() failed (%d)", ret);
     }
     else {
          ret = fi_read(cxit_ep, local, good_len*2, NULL, cxit_ep_fi_addr,
                    0, key_val, NULL);
          cr_assert_eq(ret, FI_SUCCESS, "fi_read() failed (%d)", ret);
     }

     if (use_cq) {
          /* Wait for async event indicating data has been sent */
          ret = cxit_await_completion(cxit_tx_cq, &cqe);
          cr_assert_eq(ret, -FI_EAVAIL, "Unexpected RMA success %d", ret);
          ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
          cr_assert(ret == 1);
          cr_assert_eq(err.err, FI_EIO, "Error return %d", err.err);
     } else {
          while (fi_cntr_readerr(write ? cxit_write_cntr : cxit_read_cntr) != 1)
               ;
     }

     mr_destroy(&remote);
     free(local);
}

Test(rma, read_mr_overrun_cq)
{
     mr_overrun(false, true);
}

Test(rma, write_mr_overrun_cq)
{
     mr_overrun(true, true);
}

Test(rma, read_mr_overrun_cntr)
{
     mr_overrun(false, false);
}

Test(rma, write_mr_overrun_cntr)
{
     mr_overrun(true, false);
}

static void rma_hybrid_mr_desc_test_runner(bool write, bool cq_events)
{
	struct mem_region source_window;
	struct mem_region remote_window;
	int iters = 10;
	int send_len = 1024;
	int win_len = send_len * iters;
	uint64_t source_key = 0x2;
	uint64_t remote_key = 0x1;
	int ret;
	int i;
	struct iovec msg_iov = {};
	struct fi_rma_iov rma_iov = {};
	struct fi_msg_rma msg_rma = {};
	void *desc[1];
	struct fi_cq_tagged_entry cqe;
	uint64_t rma_flags = cq_events ? FI_TRANSMIT_COMPLETE | FI_COMPLETION :
		FI_TRANSMIT_COMPLETE;
	uint64_t cqe_flags = write ? FI_RMA | FI_WRITE : FI_RMA | FI_READ;
	struct fid_cntr *cntr = write ? cxit_write_cntr : cxit_read_cntr;

	ret = mr_create(win_len, FI_READ | FI_WRITE, 0xa, &source_key,
			&source_window);
	cr_assert(ret == FI_SUCCESS);

	desc[0] = fi_mr_desc(source_window.mr);
	cr_assert(desc[0] != NULL);

	ret = mr_create(win_len, FI_REMOTE_READ | FI_REMOTE_WRITE, 0x3,
			&remote_key, &remote_window);
	cr_assert(ret == FI_SUCCESS);

	msg_rma.msg_iov = &msg_iov;
	msg_rma.desc = desc;
	msg_rma.iov_count = 1;
	msg_rma.addr = cxit_ep_fi_addr;
	msg_rma.rma_iov = &rma_iov;
	msg_rma.rma_iov_count = 1;

	for (i = 0; i < iters; i++) {
		msg_iov.iov_base = source_window.mem + (i * send_len);
		msg_iov.iov_len = send_len;

		rma_iov.addr = i * send_len;
		rma_iov.key = remote_key;
		rma_iov.len = send_len;

		if (write)
			ret = fi_writemsg(cxit_ep, &msg_rma, rma_flags);
		else
			ret = fi_readmsg(cxit_ep, &msg_rma, rma_flags);
		cr_assert_eq(ret, FI_SUCCESS, "Bad rc=%d\n", ret);
	}

	ret = fi_cntr_wait(cntr, iters, 1000);
	cr_assert(ret == FI_SUCCESS);

	if (cq_events) {
		for (i = 0; i < iters; i++) {
			ret = cxit_await_completion(cxit_tx_cq, &cqe);
			cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

			validate_tx_event(&cqe, cqe_flags, NULL);
		}
	}

	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	for (i = 0; i < win_len; i++)
		cr_assert_eq(source_window.mem[i], remote_window.mem[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     source_window.mem[i], remote_window.mem[i]);

	mr_destroy(&source_window);
	mr_destroy(&remote_window);
}

TestSuite(rma_hybrid_mr_desc, .init = cxit_setup_rma_hybrid_mr_desc,
	  .fini = cxit_teardown_rma, .timeout = CXIT_DEFAULT_TIMEOUT);

Test(rma_hybrid_mr_desc, non_inject_selective_completion_write)
{
	rma_hybrid_mr_desc_test_runner(true, false);
}

Test(rma_hybrid_mr_desc, selective_completion_read)
{
	rma_hybrid_mr_desc_test_runner(false, false);
}

Test(rma_hybrid_mr_desc, non_inject_completion_write)
{
	rma_hybrid_mr_desc_test_runner(true, true);
}

Test(rma_hybrid_mr_desc, completion_read)
{
	rma_hybrid_mr_desc_test_runner(false, true);
}

static void rma_hybrid_invalid_addr_mr_desc_test_runner(bool write,
							bool cq_events)
{
	struct mem_region source_window;
	struct mem_region remote_window;
	int send_len = 1024;
	uint64_t source_key = 0x2;
	uint64_t remote_key = 0x1;
	int ret;
	struct iovec msg_iov = {};
	struct fi_rma_iov rma_iov = {};
	struct fi_msg_rma msg_rma = {};
	void *desc[1];
	struct fi_cq_tagged_entry cqe;
	struct fi_cq_err_entry err;
	uint64_t rma_flags = cq_events ? FI_TRANSMIT_COMPLETE | FI_COMPLETION :
		FI_TRANSMIT_COMPLETE;
	struct fid_cntr *cntr = write ? cxit_write_cntr : cxit_read_cntr;

	ret = mr_create(send_len, FI_READ | FI_WRITE, 0xa, &source_key,
			&source_window);
	cr_assert(ret == FI_SUCCESS);

	desc[0] = fi_mr_desc(source_window.mr);
	cr_assert(desc[0] != NULL);

	ret = mr_create(send_len, FI_REMOTE_READ | FI_REMOTE_WRITE, 0x3,
			&remote_key, &remote_window);
	cr_assert(ret == FI_SUCCESS);

	msg_rma.msg_iov = &msg_iov;
	msg_rma.desc = desc;
	msg_rma.iov_count = 1;
	msg_rma.addr = cxit_ep_fi_addr;
	msg_rma.rma_iov = &rma_iov;
	msg_rma.rma_iov_count = 1;

	/* Generate invalid memory address. */
	msg_iov.iov_base = source_window.mem + 0xfffffffff;
	msg_iov.iov_len = send_len;

	rma_iov.key = remote_key;
	rma_iov.len = send_len;

	if (write)
		ret = fi_writemsg(cxit_ep, &msg_rma, rma_flags);
	else
		ret = fi_readmsg(cxit_ep, &msg_rma, rma_flags);
	cr_assert_eq(ret, FI_SUCCESS, "Bad rc=%d\n", ret);

	while (fi_cntr_readerr(cntr) != 1)
		;

	/* No target event should be generated. */
	ret = fi_cq_read(cxit_rx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	/* There should be an source error entry. */
	do {
		ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	} while (ret == -FI_EAGAIN);
	cr_assert(ret == -FI_EAVAIL);

	/* Expect a source error. */
	ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
	cr_assert(ret == 1);

	/* Expect no other events. */
	ret = fi_cq_read(cxit_tx_cq, &cqe, 1);
	cr_assert(ret == -FI_EAGAIN);

	mr_destroy(&source_window);
	mr_destroy(&remote_window);
}

Test(rma_hybrid_mr_desc, invalid_addr_non_inject_selective_completion_write)
{
	rma_hybrid_invalid_addr_mr_desc_test_runner(true, false);
}

Test(rma_hybrid_mr_desc, invalid_addr_selective_completion_read)
{
	rma_hybrid_invalid_addr_mr_desc_test_runner(false, false);
}

Test(rma_hybrid_mr_desc, invalid_addr_non_inject_completion_write)
{
	rma_hybrid_invalid_addr_mr_desc_test_runner(true, true);
}

Test(rma_hybrid_mr_desc, invalid_addr_completion_read)
{
	rma_hybrid_invalid_addr_mr_desc_test_runner(false, true);
}

void cxit_rma_setup_tx_alias_no_fence(void)
{
	int ret;
	uint64_t order = FI_ORDER_RMA_WAW;

	cxit_setup_getinfo();
	cxit_fi_hints->caps = CXIP_EP_PRI_CAPS;
	cxit_setup_tx_alias_rma_dc();

	/* Set WAW ordering */
	ret = fi_set_val(&cxit_tx_alias_ep->fid, FI_OPT_CXI_SET_MSG_ORDER,
			 (void *)&order);
	cr_assert_eq(ret, FI_SUCCESS, "fi_set_val(FI_OPT_SET_MSG_ORDER)");
}

/* RMA TX Alias capability */
TestSuite(rma_tx_alias, .init = cxit_rma_setup_tx_alias_no_fence,
	  .fini = cxit_teardown_tx_alias_rma, .timeout = CXIT_DEFAULT_TIMEOUT);

Test(rma_tx_alias, flush)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	uint64_t flags = FI_DELIVERY_COMPLETE;
	uint64_t flushes_start;
	uint64_t flushes_end;

	ret = cxit_dom_read_cntr(C_CNTR_IXE_DMAWR_FLUSH_REQS,
				 &flushes_start, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Send 8 bytes from send buffer data to RMA window 0 at FI address 0
	 * (self)
	 */
	ret = fi_writemsg(cxit_tx_alias_ep, &msg, flags);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg failed %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);

	ret = cxit_dom_read_cntr(C_CNTR_IXE_DMAWR_FLUSH_REQS,
				 &flushes_end, NULL, true);
	cr_assert_eq(ret, FI_SUCCESS, "cntr_read failed: %d\n", ret);
	cr_assert(flushes_end > flushes_start);
}

Test(rma_tx_alias, weak_fence)
{
	int ret;
	uint8_t *send_buf;
	int win_len = 0x1000;
	int send_len = 8;
	int i;
	struct mem_region mem_window;
	uint64_t key_val = RMA_WIN_KEY;
	struct fi_cq_tagged_entry cqe;
	struct fi_msg_rma msg = {};
	struct iovec iov[1];
	struct fi_rma_iov rma[1];
	uint64_t flags = FI_DELIVERY_COMPLETE;

	send_buf = calloc(1, win_len);
	cr_assert_not_null(send_buf, "send_buf alloc failed");

	for (i = 0; i < send_len*2; i++)
		send_buf[i] = i;

	mr_create(win_len, FI_REMOTE_WRITE, 0x44, &key_val, &mem_window);

	iov[0].iov_base = send_buf;
	iov[0].iov_len = send_len;

	rma[0].addr = 0;
	rma[0].len = send_len;
	rma[0].key = key_val;

	msg.msg_iov = iov;
	msg.iov_count = 1;
	msg.rma_iov = rma;
	msg.rma_iov_count = 1;
	msg.addr = cxit_ep_fi_addr;

	/* Verify FI_FENCE can not be done with original EP */
	ret = fi_writemsg(cxit_ep, &msg, flags | FI_FENCE);
	cr_assert_eq(ret, -FI_EINVAL, "fi_writemsg FI_FENCE ret %d", ret);

	/* Verify FI_CXI_WEAK_FENCE can be done with original EP */
	ret = fi_writemsg(cxit_ep, &msg, flags | FI_CXI_WEAK_FENCE);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg FI_WEAK_FENCE ret %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	/* Verifiy FI_CXI_WEAK_FENCE can be done with alias EP */
	rma[0].addr = send_len;
	iov[0].iov_base = send_buf + send_len;
	ret = fi_writemsg(cxit_tx_alias_ep, &msg, flags | FI_CXI_WEAK_FENCE);
	cr_assert_eq(ret, FI_SUCCESS, "fi_writemsg FI_WEAK_FENCE ret %d", ret);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);

	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < send_len * 2; i++)
		cr_assert_eq(mem_window.mem[i], send_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     mem_window.mem[i], send_buf[i]);

	mr_destroy(&mem_window);
	free(send_buf);
}

TestSuite(rma_mr_event, .init = cxit_setup_rma, .fini = cxit_teardown_rma,
	  .timeout = CXIT_DEFAULT_TIMEOUT);

/* Test that use of stale MR keys cannot access cached memory */
Test(rma_mr_event, stale_key)
{
	int ret;
	long i;
	struct fi_cq_err_entry err;
	struct fi_cq_tagged_entry cqe;
	struct fid_mr *mr;
	struct cxip_mr *cxip_mr;
	uint8_t *src_buf;
	uint8_t *src_buf2;
	uint8_t *tgt_buf;
	int src_len = 8;
	int tgt_len = 4096;
	uint64_t key_val = 200;

	src_buf = malloc(src_len);
	cr_assert_not_null(src_buf, "src_buf alloc failed");
	src_buf2 = malloc(src_len);
	cr_assert_not_null(src_buf2, "src_buf2 alloc failed");
	tgt_buf = calloc(1, tgt_len);
	cr_assert_not_null(tgt_buf, "tgt_buf alloc failed");

	for (i = 0; i < src_len; i++) {
		src_buf[i] = 0xb1 * i;
		src_buf2[i] = 0xa1 * i;
	}

	/* Create MR */
	ret = fi_mr_reg(cxit_domain, tgt_buf, tgt_len, FI_REMOTE_WRITE, 0,
			key_val, 0, &mr, NULL);
	cr_assert(ret == FI_SUCCESS);

	/* We known cached FI_MR_PROV_KEY cannot support this
	 * level of robustness, so just skip FI_MR_PROV_KEY
	 * unless FI_CXI_MR_MATCH_EVENTS is enabled.
	 */
	cxip_mr = container_of(mr, struct cxip_mr, mr_fid);
	if (cxit_fi->domain_attr->mr_mode & FI_MR_PROV_KEY &&
	    !cxip_mr->count_events) {
		fi_close(&mr->fid);
		goto done;
	}

	ret = fi_mr_bind(mr, &cxit_ep->fid, 0);
	cr_assert(ret == FI_SUCCESS);

	ret = fi_mr_enable(mr);
	cr_assert(ret == FI_SUCCESS);

	if (cxit_fi->domain_attr->mr_mode & FI_MR_PROV_KEY)
		key_val = fi_mr_key(mr);

	ret = fi_write(cxit_ep, src_buf, src_len, NULL,
		       cxit_ep_fi_addr, 0, key_val, NULL);
	cr_assert(ret == FI_SUCCESS);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, 1, "fi_cq_read failed %d", ret);
	validate_tx_event(&cqe, FI_RMA | FI_WRITE, NULL);

	/* Validate sent data */
	for (int i = 0; i < src_len; i++)
		cr_assert_eq(tgt_buf[i], src_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     tgt_buf[i], src_buf[i]);

	/* Close MR but leave memory backing it allocated/cached */
	fi_close(&mr->fid);

	/* Try to access using stale key */
	ret = fi_write(cxit_ep, src_buf2, src_len, NULL,
		       cxit_ep_fi_addr, 0, key_val, NULL);
	cr_assert(ret == FI_SUCCESS);

	/* Wait for async event indicating data has been sent */
	ret = cxit_await_completion(cxit_tx_cq, &cqe);
	cr_assert_eq(ret, -FI_EAVAIL, "Unexpected RMA success %d", ret);

	ret = fi_cq_readerr(cxit_tx_cq, &err, 1);
	cr_assert_eq(ret, 1);
	cr_assert_eq(err.err, FI_EIO, "Error return %d", err.err);

	/* Verfiy data was not modified with src_buf2 data */
	for (int i = 0; i < src_len; i++)
		cr_assert_eq(tgt_buf[i], src_buf[i],
			     "data mismatch, element: (%d) %02x != %02x\n", i,
			     tgt_buf[i], src_buf[i]);

done:
	free(tgt_buf);
	free(src_buf);
	free(src_buf2);
}

/* Note: the FI_PROTO_CXI_RNR should not alter the standard RMA
 * and Atomic functions. Perform a limited set of writes and reads to
 * verify this.
 */
TestSuite(rnr_rma, .init = cxit_setup_rnr_msg_ep,
	  .fini = cxit_teardown_msg, .timeout = CXIT_DEFAULT_TIMEOUT);

Test(rnr_rma, simple_write)
{
	simple_write();
}

Test(rnr_rma, simple_read)
{
	simple_read();
}
