/**
* Copyright (C) Mellanox Technologies Ltd. 2001-2014. ALL RIGHTS RESERVED.
* Copyright (C) UT-Battelle, LLC. 2014. ALL RIGHTS RESERVED.
* See file LICENSE for terms.
*/
#include <uct/ib/test_ib.h>
test_uct_ib::test_uct_ib() : m_e1(NULL), m_e2(NULL) { }
void test_uct_ib::create_connected_entities() {
m_e1 = uct_test::create_entity(0);
m_e2 = uct_test::create_entity(0);
m_entities.push_back(m_e1);
m_entities.push_back(m_e2);
m_e1->connect(0, *m_e2, 0);
m_e2->connect(0, *m_e1, 0);
}
void test_uct_ib::init() {
uct_test::init();
create_connected_entities();
test_uct_ib::m_ib_am_handler_counter = 0;
}
ucs_status_t test_uct_ib::ib_am_handler(void *arg, void *data,
size_t length, unsigned flags) {
recv_desc_t *my_desc = (recv_desc_t *) arg;
uint64_t *test_ib_hdr = (uint64_t *) data;
uint64_t *actual_data = (uint64_t *) test_ib_hdr + 1;
unsigned data_length = length - sizeof(test_ib_hdr);
my_desc->length = data_length;
if (*test_ib_hdr == 0xbeef) {
memcpy(my_desc + 1, actual_data , data_length);
}
++test_uct_ib::m_ib_am_handler_counter;
return UCS_OK;
}
void test_uct_ib::send_recv_short() {
size_t start_am_counter = test_uct_ib::m_ib_am_handler_counter;
uint64_t send_data = 0xdeadbeef;
uint64_t test_ib_hdr = 0xbeef;
recv_desc_t *recv_buffer;
ucs_status_t status;
check_caps_skip(UCT_IFACE_FLAG_AM_SHORT);
recv_buffer = (recv_desc_t *) malloc(sizeof(*recv_buffer) + sizeof(uint64_t));
recv_buffer->length = 0; /* Initialize length to 0 */
/* set a callback for the uct to invoke for receiving the data */
uct_iface_set_am_handler(m_e2->iface(), 0, ib_am_handler, recv_buffer, 0);
/* send the data */
status = uct_ep_am_short(m_e1->ep(0), 0, test_ib_hdr,
&send_data, sizeof(send_data));
EXPECT_TRUE((status == UCS_OK) || (status == UCS_INPROGRESS));
flush();
wait_for_value(&test_uct_ib::m_ib_am_handler_counter,
start_am_counter + 1, true);
ASSERT_EQ(sizeof(send_data), recv_buffer->length);
EXPECT_EQ(send_data, *(uint64_t*)(recv_buffer+1));
free(recv_buffer);
}
size_t test_uct_ib::m_ib_am_handler_counter = 0;
class test_uct_ib_addr : public test_uct_ib {
public:
uct_ib_iface_config_t *ib_config() {
return ucs_derived_of(m_iface_config, uct_ib_iface_config_t);
}
void test_address_pack(uint64_t subnet_prefix) {
uct_ib_iface_t *iface = ucs_derived_of(m_e1->iface(), uct_ib_iface_t);
static const uint16_t lid_in = 0x1ee7;
union ibv_gid gid_in, gid_out;
uct_ib_address_t *ib_addr;
uint16_t lid_out;
ib_addr = (uct_ib_address_t*)malloc(uct_ib_iface_address_size(iface));
gid_in.global.subnet_prefix = subnet_prefix;
gid_in.global.interface_id = 0xdeadbeef;
uct_ib_iface_address_pack(iface, &gid_in, lid_in, ib_addr);
uct_ib_address_unpack(ib_addr, &lid_out, &gid_out);
if (uct_ib_iface_is_roce(iface)) {
EXPECT_TRUE(iface->config.force_global_addr);
} else {
EXPECT_EQ(lid_in, lid_out);
}
if (ib_config()->is_global) {
EXPECT_EQ(gid_in.global.subnet_prefix, gid_out.global.subnet_prefix);
EXPECT_EQ(gid_in.global.interface_id, gid_out.global.interface_id);
}
free(ib_addr);
}
void test_fill_ah_attr(uint64_t subnet_prefix) {
uct_ib_iface_t *iface = ucs_derived_of(m_e1->iface(), uct_ib_iface_t);
static const uint16_t lid = 0x1ee7;
union ibv_gid gid;
struct ibv_ah_attr ah_attr;
ASSERT_EQ(iface->config.force_global_addr,
ib_config()->is_global || uct_ib_iface_is_roce(iface));
gid.global.subnet_prefix = subnet_prefix ?: iface->gid.global.subnet_prefix;
gid.global.interface_id = 0xdeadbeef;
uct_ib_iface_fill_ah_attr_from_gid_lid(iface, lid, &gid, &ah_attr);
if (uct_ib_iface_is_roce(iface)) {
/* in case of roce, should be global */
EXPECT_TRUE(ah_attr.is_global);
} else if (ib_config()->is_global) {
/* in case of global address is forced - ah_attr should use GRH */
EXPECT_TRUE(ah_attr.is_global);
} else if (iface->gid.global.subnet_prefix == gid.global.subnet_prefix) {
/* in case of subnets are same - ah_attr depend from forced/nonforced GRH */
EXPECT_FALSE(ah_attr.is_global);
} else if (iface->gid.global.subnet_prefix != gid.global.subnet_prefix) {
/* in case of subnets are different - ah_attr should use GRH */
EXPECT_TRUE(ah_attr.is_global);
}
}
};
UCS_TEST_P(test_uct_ib_addr, address_pack) {
test_address_pack(UCT_IB_LINK_LOCAL_PREFIX);
test_address_pack(UCT_IB_SITE_LOCAL_PREFIX | htobe64(0x7200));
test_address_pack(0xdeadfeedbeefa880ul);
}
UCS_TEST_P(test_uct_ib_addr, fill_ah_attr) {
test_fill_ah_attr(UCT_IB_LINK_LOCAL_PREFIX);
test_fill_ah_attr(UCT_IB_SITE_LOCAL_PREFIX | htobe64(0x7200));
test_fill_ah_attr(0xdeadfeedbeefa880ul);
test_fill_ah_attr(0l);
}
UCS_TEST_P(test_uct_ib_addr, address_pack_global, "IB_IS_GLOBAL=y") {
test_address_pack(UCT_IB_LINK_LOCAL_PREFIX);
test_address_pack(UCT_IB_SITE_LOCAL_PREFIX | htobe64(0x7200));
test_address_pack(0xdeadfeedbeefa880ul);
}
UCS_TEST_P(test_uct_ib_addr, fill_ah_attr_global, "IB_IS_GLOBAL=y") {
test_fill_ah_attr(UCT_IB_LINK_LOCAL_PREFIX);
test_fill_ah_attr(UCT_IB_SITE_LOCAL_PREFIX | htobe64(0x7200));
test_fill_ah_attr(0xdeadfeedbeefa880ul);
test_fill_ah_attr(0l);
}
UCT_INSTANTIATE_IB_TEST_CASE(test_uct_ib_addr);
test_uct_ib_with_specific_port::test_uct_ib_with_specific_port() {
m_ibctx = NULL;
m_port = 0;
m_dev_name = "";
memset(&m_port_attr, 0, sizeof(m_port_attr));
}
void test_uct_ib_with_specific_port::init() {
size_t colon_pos = GetParam()->dev_name.find(":");
std::string port_num_str;
m_dev_name = GetParam()->dev_name.substr(0, colon_pos);
port_num_str = GetParam()->dev_name.substr(colon_pos + 1);
/* port number */
if (sscanf(port_num_str.c_str(), "%d", &m_port) != 1) {
UCS_TEST_ABORT("Failed to get the port number on device: " << m_dev_name);
}
std::string abort_reason =
"The requested device " + m_dev_name +
" wasn't found in the device list.";
struct ibv_device **device_list;
int i, num_devices;
/* get device list */
device_list = ibv_get_device_list(&num_devices);
if (device_list == NULL) {
abort_reason = "Failed to get the device list.";
num_devices = 0;
}
/* search for the given device in the device list */
for (i = 0; i < num_devices; ++i) {
if (strcmp(device_list[i]->name, m_dev_name.c_str())) {
continue;
}
/* found this dev_name on the host - open it */
m_ibctx = ibv_open_device(device_list[i]);
if (m_ibctx == NULL) {
abort_reason = "Failed to open the device.";
}
break;
}
ibv_free_device_list(device_list);
if (m_ibctx == NULL) {
UCS_TEST_ABORT(abort_reason);
}
if (ibv_query_port(m_ibctx, m_port, &m_port_attr) != 0) {
UCS_TEST_ABORT("Failed to query port " << m_port <<
"on device: " << m_dev_name);
}
try {
check_port_attr();
} catch (...) {
test_uct_ib_with_specific_port::cleanup();
throw;
}
}
void test_uct_ib_with_specific_port::cleanup() {
if (m_ibctx != NULL) {
ibv_close_device(m_ibctx);
m_ibctx = NULL;
}
}
class test_uct_ib_lmc : public test_uct_ib_with_specific_port {
public:
void init() {
test_uct_ib_with_specific_port::init();
test_uct_ib::init();
}
void cleanup() {
test_uct_ib::cleanup();
test_uct_ib_with_specific_port::cleanup();
}
void check_port_attr() {
/* check if a non zero lmc is set on the port */
if (!m_port_attr.lmc) {
UCS_TEST_SKIP_R("lmc is set to zero on an IB port");
}
}
};
UCS_TEST_P(test_uct_ib_lmc, non_default_lmc, "IB_LID_PATH_BITS=1") {
send_recv_short();
}
UCT_INSTANTIATE_IB_TEST_CASE(test_uct_ib_lmc);
class test_uct_ib_gid_idx : public test_uct_ib_with_specific_port {
public:
void init() {
test_uct_ib_with_specific_port::init();
test_uct_ib::init();
}
void cleanup() {
test_uct_ib::cleanup();
test_uct_ib_with_specific_port::cleanup();
}
void check_port_attr() {
std::stringstream device_str;
device_str << ibv_get_device_name(m_ibctx->device) << ":" << m_port;
if (!IBV_PORT_IS_LINK_LAYER_ETHERNET(&m_port_attr)) {
UCS_TEST_SKIP_R(device_str.str() + " is not Ethernet");
}
union ibv_gid gid;
uct_ib_md_config_t *md_config =
ucs_derived_of(m_md_config, uct_ib_md_config_t);
ucs::handle<uct_md_h> uct_md;
uct_ib_md_t *ib_md;
ucs_status_t status;
uint8_t gid_index;
UCS_TEST_CREATE_HANDLE(uct_md_h, uct_md, uct_ib_md_close, uct_ib_md_open,
&uct_ib_component,
ibv_get_device_name(m_ibctx->device), m_md_config);
ib_md = ucs_derived_of(uct_md, uct_ib_md_t);
status = uct_ib_device_select_gid_index(&ib_md->dev, m_port,
md_config->ext.gid_index,
&gid_index);
ASSERT_UCS_OK(status);
device_str << " gid index " << static_cast<int>(gid_index);
/* check the gid index */
if (ibv_query_gid(m_ibctx, m_port, gid_index, &gid) != 0) {
UCS_TEST_ABORT("failed to query " + device_str.str());
}
/* check if the gid is valid to use */
if (uct_ib_device_is_gid_raw_empty(gid.raw)) {
UCS_TEST_SKIP_R(device_str.str() + " is empty");
}
if (!uct_ib_device_test_roce_gid_index(&ib_md->dev, m_port, &gid,
gid_index)) {
UCS_TEST_SKIP_R("failed to create address handle on " +
device_str.str());
}
}
};
UCS_TEST_P(test_uct_ib_gid_idx, non_default_gid_idx, "GID_INDEX=1") {
send_recv_short();
}
UCT_INSTANTIATE_IB_TEST_CASE(test_uct_ib_gid_idx);
class test_uct_ib_utils : public ucs::test {
};
UCS_TEST_F(test_uct_ib_utils, sec_to_qp_time) {
double avg;
uint8_t qp_val;
// 0 sec
qp_val = uct_ib_to_qp_fabric_time(0);
EXPECT_EQ(1, qp_val);
// the average time defined for the [0, 1st element]
qp_val = uct_ib_to_qp_fabric_time(4.096 * pow(2, 0) / UCS_USEC_PER_SEC);
EXPECT_EQ(1, qp_val);
// the time defined for the 1st element
qp_val = uct_ib_to_qp_fabric_time(4.096 * pow(2, 1) / UCS_USEC_PER_SEC);
EXPECT_EQ(1, qp_val);
for (uint8_t index = 2; index <= UCT_IB_FABRIC_TIME_MAX; index++) {
uint8_t prev_index = index - 1;
// the time defined for the (i)th element
qp_val = uct_ib_to_qp_fabric_time(4.096 * pow(2, index) / UCS_USEC_PER_SEC);
EXPECT_EQ(index % UCT_IB_FABRIC_TIME_MAX, qp_val);
// avg = (the average time defined for the [(i - 1)th element, (i)th element])
avg = (4.096 * pow(2, prev_index) + 4.096 * pow(2, index)) * 0.5;
qp_val = uct_ib_to_qp_fabric_time(avg / UCS_USEC_PER_SEC);
EXPECT_EQ(index % UCT_IB_FABRIC_TIME_MAX, qp_val);
// the average time defined for the [(i - 1)th element, avg]
qp_val = uct_ib_to_qp_fabric_time((4.096 * pow(2, prev_index) + avg) * 0.5 / UCS_USEC_PER_SEC);
EXPECT_EQ(prev_index, qp_val);
// the average time defined for the [avg, (i)th element]
qp_val = uct_ib_to_qp_fabric_time((avg + 4.096 * pow(2, index)) * 0.5 / UCS_USEC_PER_SEC);
EXPECT_EQ(index % UCT_IB_FABRIC_TIME_MAX, qp_val);
}
}
UCS_TEST_F(test_uct_ib_utils, sec_to_rnr_time) {
double avg;
uint8_t rnr_val;
// 0 sec
rnr_val = uct_ib_to_rnr_fabric_time(0);
EXPECT_EQ(1, rnr_val);
// the average time defined for the [0, 1st element]
avg = uct_ib_qp_rnr_time_ms[1] * 0.5;
rnr_val = uct_ib_to_rnr_fabric_time(avg / UCS_MSEC_PER_SEC);
EXPECT_EQ(1, rnr_val);
for (uint8_t index = 1; index < UCT_IB_FABRIC_TIME_MAX; index++) {
uint8_t next_index = (index + 1) % UCT_IB_FABRIC_TIME_MAX;
// the time defined for the (i)th element
rnr_val = uct_ib_to_rnr_fabric_time(uct_ib_qp_rnr_time_ms[index] / UCS_MSEC_PER_SEC);
EXPECT_EQ(index, rnr_val);
// avg = (the average time defined for the [(i)th element, (i + 1)th element])
avg = (uct_ib_qp_rnr_time_ms[index] + uct_ib_qp_rnr_time_ms[next_index]) * 0.5;
rnr_val = uct_ib_to_rnr_fabric_time(avg / UCS_MSEC_PER_SEC);
EXPECT_EQ(next_index, rnr_val);
// the average time defined for the [(i)th element, avg]
rnr_val = uct_ib_to_rnr_fabric_time((uct_ib_qp_rnr_time_ms[index] + avg) * 0.5 / UCS_MSEC_PER_SEC);
EXPECT_EQ(index, rnr_val);
// the average time defined for the [avg, (i + 1)th element]
rnr_val = uct_ib_to_rnr_fabric_time((avg + uct_ib_qp_rnr_time_ms[next_index]) *
0.5 / UCS_MSEC_PER_SEC);
EXPECT_EQ(next_index, rnr_val);
}
// the time defined for the biggest value
rnr_val = uct_ib_to_rnr_fabric_time(uct_ib_qp_rnr_time_ms[0] / UCS_MSEC_PER_SEC);
EXPECT_EQ(0, rnr_val);
// 1 sec
rnr_val = uct_ib_to_rnr_fabric_time(1.);
EXPECT_EQ(0, rnr_val);
}
class test_uct_event_ib : public test_uct_ib {
public:
test_uct_event_ib() {
length = 8;
wakeup_fd.revents = 0;
wakeup_fd.events = POLLIN;
wakeup_fd.fd = 0;
test_ib_hdr = 0xbeef;
m_buf1 = NULL;
m_buf2 = NULL;
}
void init() {
ucs_status_t status;
test_uct_ib::init();
try {
check_caps_skip(UCT_IFACE_FLAG_PUT_SHORT | UCT_IFACE_FLAG_CB_SYNC |
UCT_IFACE_FLAG_EVENT_SEND_COMP |
UCT_IFACE_FLAG_EVENT_RECV);
} catch (...) {
test_uct_ib::cleanup();
throw;
}
/* create receiver wakeup */
status = uct_iface_event_fd_get(m_e1->iface(), &wakeup_fd.fd);
ASSERT_EQ(status, UCS_OK);
EXPECT_EQ(0, poll(&wakeup_fd, 1, 0));
m_buf1 = new mapped_buffer(length, 0x1, *m_e1);
m_buf2 = new mapped_buffer(length, 0x2, *m_e2);
/* set a callback for the uct to invoke for receiving the data */
uct_iface_set_am_handler(m_e1->iface(), 0, ib_am_handler, m_buf1->ptr(),
0);
test_uct_event_ib::bcopy_pack_count = 0;
}
static size_t pack_cb(void *dest, void *arg) {
const mapped_buffer *buf = (const mapped_buffer *)arg;
memcpy(dest, buf->ptr(), buf->length());
++test_uct_event_ib::bcopy_pack_count;
return buf->length();
}
/* Use put_bcopy here to provide send_cq entry */
void send_msg_e1_e2(size_t count = 1) {
for (size_t i = 0; i < count; ++i) {
ssize_t status = uct_ep_put_bcopy(m_e1->ep(0), pack_cb, (void *)m_buf1,
m_buf2->addr(), m_buf2->rkey());
if (status < 0) {
ASSERT_UCS_OK((ucs_status_t)status);
}
}
}
void send_msg_e2_e1(size_t count = 1) {
for (size_t i = 0; i < count; ++i) {
ucs_status_t status = uct_ep_am_short(m_e2->ep(0), 0, test_ib_hdr,
m_buf2->ptr(), m_buf2->length());
ASSERT_UCS_OK(status);
}
}
void check_send_cq(uct_iface_t *iface, size_t val) {
uct_ib_iface_t *ib_iface = ucs_derived_of(iface, uct_ib_iface_t);
struct ibv_cq *send_cq = ib_iface->cq[UCT_IB_DIR_TX];
if (val != send_cq->comp_events_completed) {
uint32_t completed_evt = send_cq->comp_events_completed;
/* need this call to acknowledge the completion to prevent iface dtor hung*/
ibv_ack_cq_events(ib_iface->cq[UCT_IB_DIR_TX], 1);
UCS_TEST_ABORT("send_cq->comp_events_completed have to be 1 but the value "
<< completed_evt);
}
}
void check_recv_cq(uct_iface_t *iface, size_t val) {
uct_ib_iface_t *ib_iface = ucs_derived_of(iface, uct_ib_iface_t);
struct ibv_cq *recv_cq = ib_iface->cq[UCT_IB_DIR_RX];
if (val != recv_cq->comp_events_completed) {
uint32_t completed_evt = recv_cq->comp_events_completed;
/* need this call to acknowledge the completion to prevent iface dtor hung*/
ibv_ack_cq_events(ib_iface->cq[UCT_IB_DIR_RX], 1);
UCS_TEST_ABORT("recv_cq->comp_events_completed have to be 1 but the value "
<< completed_evt);
}
}
void cleanup() {
delete(m_buf1);
delete(m_buf2);
test_uct_ib::cleanup();
}
protected:
static const unsigned EVENTS = UCT_EVENT_RECV | UCT_EVENT_SEND_COMP;
struct pollfd wakeup_fd;
size_t length;
uint64_t test_ib_hdr;
mapped_buffer *m_buf1, *m_buf2;
static size_t bcopy_pack_count;
};
size_t test_uct_event_ib::bcopy_pack_count = 0;
UCS_TEST_P(test_uct_event_ib, tx_cq)
{
ucs_status_t status;
status = uct_iface_event_arm(m_e1->iface(), EVENTS);
ASSERT_EQ(status, UCS_OK);
/* check initial state of the fd and [send|recv]_cq */
EXPECT_EQ(0, poll(&wakeup_fd, 1, 0));
check_send_cq(m_e1->iface(), 0);
check_recv_cq(m_e1->iface(), 0);
/* send the data */
send_msg_e1_e2();
/* make sure the file descriptor is signaled once */
ASSERT_EQ(1, poll(&wakeup_fd, 1, 1000*ucs::test_time_multiplier()));
status = uct_iface_event_arm(m_e1->iface(), EVENTS);
ASSERT_EQ(status, UCS_ERR_BUSY);
/* make sure [send|recv]_cq handled properly */
check_send_cq(m_e1->iface(), 1);
check_recv_cq(m_e1->iface(), 0);
m_e1->flush();
}
UCS_TEST_P(test_uct_event_ib, txrx_cq)
{
const size_t msg_count = 1;
ucs_status_t status;
status = uct_iface_event_arm(m_e1->iface(), EVENTS);
ASSERT_EQ(UCS_OK, status);
/* check initial state of the fd and [send|recv]_cq */
EXPECT_EQ(0, poll(&wakeup_fd, 1, 0));
check_send_cq(m_e1->iface(), 0);
check_recv_cq(m_e1->iface(), 0);
/* send the data */
send_msg_e1_e2(msg_count);
send_msg_e2_e1(msg_count);
twait(150); /* Let completion to be generated */
/* Make sure all messages delivered */
while ((test_uct_ib::m_ib_am_handler_counter < msg_count) ||
(test_uct_event_ib::bcopy_pack_count < msg_count)) {
progress();
}
/* make sure the file descriptor is signaled */
ASSERT_EQ(1, poll(&wakeup_fd, 1, 1000*ucs::test_time_multiplier()));
/* Acknowledge all the requests */
short_progress_loop();
status = uct_iface_event_arm(m_e1->iface(), EVENTS);
ASSERT_EQ(UCS_ERR_BUSY, status);
/* make sure [send|recv]_cq handled properly */
check_send_cq(m_e1->iface(), 1);
check_recv_cq(m_e1->iface(), 1);
m_e1->flush();
m_e2->flush();
}
UCT_INSTANTIATE_IB_TEST_CASE(test_uct_event_ib);