[PATCH 09/12] cxiverbs: cover identity isolation and polling and event traffic

From: Doug Ledford

Date: Sat Oct 31 2026 - 15:29:06 EST


Add cxi_le_test, a test for the provider. Run with no arguments it
exercises the list entry and key model and needs no hardware. With
--resources DEVICE it drives a real device (service, LNI, PID, QP,
job and key lifecycle, VNI isolation, send/receive data and
completions) and with --csr DEVICE the privileged CSR mapping. The
hardware modes require root and an idle device.

Setting CXI_TEST_WRONG_VNI in the environment adds a negative pass
in which a receive posted under one job must not match traffic from
another.

Co-developed-by: Iraimani P <iraimani.pavadai@xxxxxxx>
Signed-off-by: Iraimani P <iraimani.pavadai@xxxxxxx>
Signed-off-by: Doug Ledford <doug.ledford@xxxxxxx>
---
providers/cxi/tests/cxi_le_test.c | 2014 +++++++++++++++++++++++++++++++++++++
tests/base.py | 47 +
tests/ru_base.py | 33 +-
tests/test_addr.py | 4 +-
tests/test_device.py | 12 +-
tests/test_dmah.py | 70 +-
tests/test_mr.py | 8 +-
tests/test_qp.py | 7 +-
tests/test_ru.py | 136 ++-
9 files changed, 2197 insertions(+), 134 deletions(-)

diff --git a/providers/cxi/tests/cxi_le_test.c b/providers/cxi/tests/cxi_le_test.c
new file mode 100644
index 000000000000..5903f18b50ba
--- /dev/null
+++ b/providers/cxi/tests/cxi_le_test.c
@@ -0,0 +1,2014 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Copyright 2026 Hewlett Packard Enterprise Development LP
+ *
+ * List-entry encoding for RU QPs. No hardware needed: entries are checked
+ * field by field and against a model of how a matching PTE selects them.
+ */
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ccan/array_size.h>
+
+#include "../cxi.h"
+#include "../cxi_le.h"
+#include "../cxidv.h"
+#include "../cxidv_telem.h"
+
+static int test_destroy_qp_command(struct ibv_qp *qp);
+#define ibv_cmd_destroy_qp test_destroy_qp_command
+#include "../verbs.c"
+#undef ibv_cmd_destroy_qp
+
+bool is_cxi_dev(struct ibv_device *device)
+{
+ const struct verbs_device *provider = verbs_get_device(device);
+
+ return provider->ops->name && !strcmp(provider->ops->name, "cxiverbs");
+}
+
+static bool fail_destroy_qp_command;
+
+static int test_destroy_qp_command(struct ibv_qp *qp)
+{
+ return fail_destroy_qp_command ? EBUSY : ibv_cmd_destroy_qp(qp);
+}
+
+static int failed_tests;
+
+#define EXPECT_EQ(expected, actual) \
+ ({ \
+ unsigned long long _e = (unsigned long long)(expected); \
+ unsigned long long _a = (unsigned long long)(actual); \
+ if (_e != _a) { \
+ printf(" FAIL at line %d: %s != %s (0x%llx != 0x%llx)\n", __LINE__, \
+ #expected, #actual, _e, _a); \
+ failed_tests++; \
+ } \
+ })
+
+#define EXPECT_TRUE(actual) EXPECT_EQ(1, !!(actual))
+#define EXPECT_FALSE(actual) EXPECT_EQ(0, !!(actual))
+
+static void correctness_result(const char *name, int before)
+{
+ printf("%s correctness: %s\n", failed_tests == before ? "PASS" : "FAIL", name);
+}
+
+struct address_test {
+ struct cxi_context context;
+ struct cxi_qp qp;
+ struct cxi_ibv_cq cq;
+ struct ibv_job job;
+ pthread_mutex_t lock;
+ struct ibv_ah_attr_ex address;
+ unsigned int capacity;
+ unsigned int queries;
+ unsigned int failures;
+ unsigned int submitted;
+ bool present;
+ bool check_lock;
+};
+
+static struct ibv_job *mock_alloc_job(struct ibv_context *context, struct ibv_job_attr *attr,
+ void *user_context)
+{
+ struct address_test *test =
+ container_of(context, struct address_test, context.verbs_ctx.context);
+
+ (void)user_context;
+ test->capacity =
+ attr->comp_mask & IBV_JOB_ATTR_MAX_ADDR_ENTRIES ? attr->max_addr_entries : 0;
+ return &test->job;
+}
+
+static int mock_query_addr(struct ibv_job *job, unsigned int index, struct ibv_ah_attr_ex *address)
+{
+ struct address_test *test = container_of(job, struct address_test, job);
+ int ret = 0;
+
+ if (test->check_lock) {
+ ret = pthread_spin_trylock(&test->qp.sq_lock);
+ if (ret)
+ return EDEADLK;
+ pthread_spin_unlock(&test->qp.sq_lock);
+ }
+ pthread_mutex_lock(&test->lock);
+ test->queries++;
+ if (index >= test->capacity)
+ ret = EINVAL;
+ else if (!test->present)
+ ret = ENOENT;
+ else
+ *address = test->address;
+ pthread_mutex_unlock(&test->lock);
+ return ret;
+}
+
+static int mock_insert_addr(struct ibv_job *job, struct ibv_ah_attr_ex *address, unsigned int index)
+{
+ struct address_test *test = container_of(job, struct address_test, job);
+ int ret = 0;
+
+ pthread_mutex_lock(&test->lock);
+ if (index >= test->capacity) {
+ ret = EINVAL;
+ } else if (test->present) {
+ ret = EBUSY;
+ } else {
+ test->address = *address;
+ test->present = true;
+ }
+ pthread_mutex_unlock(&test->lock);
+ return ret;
+}
+
+static int mock_remove_addr(struct ibv_job *job, unsigned int index)
+{
+ struct address_test *test = container_of(job, struct address_test, job);
+ int ret = 0;
+
+ pthread_mutex_lock(&test->lock);
+ if (index >= test->capacity)
+ ret = EINVAL;
+ else if (!test->present)
+ ret = ENOENT;
+ else
+ test->present = false;
+ pthread_mutex_unlock(&test->lock);
+ return ret;
+}
+
+static void *mock_update_addresses(void *opaque)
+{
+ struct address_test *test = opaque;
+
+ for (unsigned int index = 0; index < 1000; index++) {
+ pthread_mutex_lock(&test->lock);
+ test->present = index & 1;
+ test->address.remote_qpn = index;
+ pthread_mutex_unlock(&test->lock);
+ }
+ return NULL;
+}
+
+static void *mock_submit_indexed(void *opaque)
+{
+ struct address_test *test = opaque;
+ unsigned int failures = 0;
+ unsigned int submitted = 0;
+
+ for (unsigned int index = 0; index < 1000; index++) {
+ struct ibv_send_wr request = {
+ .opcode = IBV_WR_SEND,
+ };
+ struct ibv_sge sge = { .addr = 0x1000, .length = 64, .lkey = 1 };
+ struct ibv_send_wr *bad = NULL;
+ int ret;
+
+ request.sg_list = &sge;
+ request.num_sge = 1;
+ if (index & 1) {
+ struct ibv_qp_ex *extended = &test->qp.vqp.qp_ex;
+
+ cxi_wr_start(extended);
+ cxi_wr_send(extended);
+ cxi_wr_set_ru_addr(extended, NULL, 0);
+ cxi_wr_set_sge32(extended, 1, 0x1000, 64);
+ ret = cxi_wr_complete(extended);
+ } else {
+ ret = cxi_post_send(&test->qp.vqp.qp, &request, &bad);
+ }
+ if (!ret)
+ submitted++;
+ else if ((ret != ENOENT && ret != ENOSPC) || (!(index & 1) && bad != &request))
+ failures++;
+ }
+ pthread_mutex_lock(&test->lock);
+ test->failures += failures;
+ test->submitted += submitted;
+ pthread_mutex_unlock(&test->lock);
+ return NULL;
+}
+
+static void cxi_test_clear_cookie(void *buffer, size_t bytes_size, uint64_t cookie)
+{
+ uint8_t *bytes = buffer;
+
+ for (size_t offset = 0; offset + sizeof(cookie) <= bytes_size; offset += sizeof(cookie)) {
+ uint64_t value;
+
+ memcpy(&value, bytes + offset, sizeof(value));
+ if (value == cookie) {
+ memset(bytes + offset, 0, sizeof(cookie));
+ return;
+ }
+ }
+
+ EXPECT_TRUE(false);
+}
+
+static void test_address_command_equivalence(struct address_test *test)
+{
+ struct cxi_cmd64 commands[32] = {}, explicit_commands[32];
+ uint64_t doorbell = 0, wrids[8], cookies[8];
+ uint32_t job_ids[8];
+ uint8_t opcodes[8];
+ bool slot_valid[8] = {};
+ struct cxi_mr mr = {
+ .va = 0x1000,
+ .iova = 0x1000,
+ .len = 64,
+ };
+ struct ibv_sge sge = { .addr = 0x1000, .length = 64, .lkey = 1 };
+ struct ibv_qp_ex *extended = &test->qp.vqp.qp_ex;
+ const enum ibv_wr_opcode operations[] = {
+ IBV_WR_SEND,
+ IBV_WR_RDMA_WRITE,
+ IBV_WR_RDMA_READ,
+ };
+ struct cxi_ah address = { .ah_ex.remote_qpn = 1 };
+
+ mr.vmr.ibv_mr.lkey = 1;
+ test->context.mr_list = &mr;
+ pthread_spin_init(&test->context.mr_list_lock, PTHREAD_PROCESS_PRIVATE);
+ test->qp.sq_size = ARRAY_SIZE(wrids);
+ test->qp.send_wrids = wrids;
+ test->qp.send_opcodes = opcodes;
+ test->qp.send_job_ids = job_ids;
+ test->qp.send_cookies = cookies;
+ test->qp.send_slot_valid = slot_valid;
+ test->context.pid_bits = C_DFA_PID_BITS_MAX;
+ test->address.remote_qpn = 1;
+
+ for (size_t operation = 0; operation < ARRAY_SIZE(operations); operation++) {
+ for (unsigned int api = 0; api < 2; api++) {
+ for (unsigned int indexed = 0; indexed < 2; indexed++) {
+ struct ibv_send_wr request = {
+ .opcode = operations[operation],
+ .sg_list = &sge,
+ .num_sge = 1,
+ };
+ struct ibv_send_wr *bad = NULL;
+
+ memset(commands, 0, sizeof(commands));
+ cxi_cq_init(&test->qp.hw_txq, commands, ARRAY_SIZE(commands),
+ &doorbell, 0);
+ test->qp.send_count = 0;
+ test->qp.send_tail = 0;
+ memset(cookies, 0, sizeof(cookies));
+ memset(slot_valid, 0, sizeof(slot_valid));
+ if (!api) {
+ if (operations[operation] == IBV_WR_SEND) {
+ request.wr.ru.ah = indexed ? NULL : &address.ah_ex;
+ request.wr.ru.addr_idx = 1;
+ } else {
+ request.wr.ru_rdma.ah =
+ indexed ? NULL : &address.ah_ex;
+ request.wr.ru_rdma.addr_idx = 1;
+ request.wr.ru_rdma.rkey64 = 1;
+ request.wr.ru_rdma.remote_addr = 0x2000;
+ }
+ EXPECT_EQ(0,
+ cxi_post_send(&test->qp.vqp.qp, &request, &bad));
+ } else {
+ cxi_wr_start(extended);
+ if (operations[operation] == IBV_WR_SEND)
+ cxi_wr_send(extended);
+ else if (operations[operation] == IBV_WR_RDMA_WRITE)
+ cxi_wr_rdma_write64(extended, 1, 0x2000);
+ else
+ cxi_wr_rdma_read64(extended, 1, 0x2000);
+ cxi_wr_set_ru_addr(extended,
+ indexed ? NULL : &address.ah_ex, 1);
+ cxi_wr_set_sge32(extended, 1, 0x1000, 64);
+ EXPECT_EQ(0, cxi_wr_complete(extended));
+ }
+ EXPECT_EQ(1, test->qp.send_count);
+ {
+ uint64_t cookie = test->qp.send_cookies[0];
+
+ cxi_test_clear_cookie(commands, sizeof(commands), cookie);
+ }
+ if (!indexed)
+ memcpy(explicit_commands, commands, sizeof(commands));
+ else
+ EXPECT_EQ(0, memcmp(explicit_commands, commands,
+ sizeof(commands)));
+ }
+ }
+ }
+ test->qp.sq_size = 0;
+ test->qp.send_count = 0;
+ test->qp.send_tail = 0;
+ test->context.mr_list = NULL;
+ pthread_spin_destroy(&test->context.mr_list_lock);
+}
+
+static void test_indexed_submission_locking(void)
+{
+ struct address_test test = {};
+ struct cxi_cmd64 *commands = calloc(4096, sizeof(*commands));
+ uint64_t doorbell = 0;
+ struct cxi_mr mr = { .va = 0x1000, .iova = 0x1000, .len = 64 };
+ uint64_t *wrids = calloc(2048, sizeof(*wrids));
+ uint64_t *cookies = calloc(2048, sizeof(*cookies));
+ uint32_t *job_ids = calloc(2048, sizeof(*job_ids));
+ uint8_t *opcodes = calloc(2048, sizeof(*opcodes));
+ bool *slot_valid = calloc(2048, sizeof(*slot_valid));
+ struct ibv_context *context = &test.context.verbs_ctx.context;
+ struct ibv_job_attr attr = {};
+ struct ibv_ah_attr_ex table_address = { .remote_qpn = 1 };
+ struct ibv_ah_attr_ex queried_address = {};
+ struct ibv_send_wr request = { .opcode = IBV_WR_SEND };
+ struct ibv_send_wr *bad = NULL;
+ struct ibv_qp_ex *extended = &test.qp.vqp.qp_ex;
+ struct cxi_ah explicit = {};
+ pthread_t updater, submitters[2];
+ unsigned int queries;
+ int before = failed_tests;
+ int ret;
+
+ EXPECT_TRUE(commands && wrids && cookies && job_ids && opcodes && slot_valid);
+ if (!commands || !wrids || !cookies || !job_ids || !opcodes || !slot_valid) {
+ free(commands);
+ free(wrids);
+ free(cookies);
+ free(job_ids);
+ free(opcodes);
+ free(slot_valid);
+ return;
+ }
+ test.context.verbs_ctx.sz = sizeof(test.context.verbs_ctx);
+ context->abi_compat = __VERBS_ABI_IS_EXTENDED;
+ test.context.verbs_ctx.alloc_job = mock_alloc_job;
+ test.context.verbs_ctx.insert_addr = mock_insert_addr;
+ test.context.verbs_ctx.remove_addr = mock_remove_addr;
+ test.context.verbs_ctx.query_addr = mock_query_addr;
+ test.qp.vqp.qp.context = context;
+ test.qp.vqp.qp.send_cq = &test.cq.verbs_cq.cq;
+ test.qp.job = &test.job;
+ test.qp.qp_slot = 0;
+ test.qp.send_cookies = cookies;
+ test.qp.send_slot_valid = slot_valid;
+ test.job.context = context;
+ atomic_init(&test.context.send_cookie_seq, 0);
+ test.check_lock = true;
+ pthread_mutex_init(&test.lock, NULL);
+ pthread_mutex_init(&test.qp.builder_lock, NULL);
+ pthread_spin_init(&test.qp.sq_lock, PTHREAD_PROCESS_PRIVATE);
+
+ EXPECT_TRUE(ibv_alloc_job(context, &attr, NULL) == &test.job);
+ EXPECT_EQ(0, test.capacity);
+ EXPECT_EQ(EINVAL, ibv_insert_addr(&test.job, &table_address, 0));
+ EXPECT_EQ(EINVAL, ibv_query_addr(&test.job, 0, &queried_address));
+ EXPECT_EQ(EINVAL, ibv_remove_addr(&test.job, 0));
+ attr.comp_mask = IBV_JOB_ATTR_MAX_ADDR_ENTRIES;
+ EXPECT_TRUE(ibv_alloc_job(context, &attr, NULL) == &test.job);
+ EXPECT_EQ(0, test.capacity);
+ attr.max_addr_entries = 2;
+ EXPECT_TRUE(ibv_alloc_job(context, &attr, NULL) == &test.job);
+ EXPECT_EQ(2, test.capacity);
+ EXPECT_EQ(ENOENT, ibv_query_addr(&test.job, 0, &queried_address));
+ EXPECT_EQ(ENOENT, ibv_remove_addr(&test.job, 0));
+ EXPECT_EQ(0, ibv_insert_addr(&test.job, &table_address, 0));
+ EXPECT_EQ(EBUSY, ibv_insert_addr(&test.job, &table_address, 0));
+ EXPECT_EQ(0, ibv_query_addr(&test.job, 0, &queried_address));
+ EXPECT_EQ(table_address.remote_qpn, queried_address.remote_qpn);
+ EXPECT_EQ(EINVAL, ibv_insert_addr(&test.job, &table_address, 2));
+ EXPECT_EQ(EINVAL, ibv_query_addr(&test.job, UINT_MAX, &queried_address));
+ EXPECT_EQ(EINVAL, ibv_remove_addr(&test.job, 2));
+ EXPECT_EQ(0, ibv_remove_addr(&test.job, 0));
+ EXPECT_EQ(ENOENT, ibv_query_addr(&test.job, 0, &queried_address));
+
+ EXPECT_EQ(ENOENT, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ EXPECT_TRUE(bad == &request);
+ test.present = true;
+ EXPECT_EQ(ENOSPC, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ request.wr.ru.addr_idx = 2;
+ EXPECT_EQ(EINVAL, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ request.wr.ru.addr_idx = 1;
+ request.opcode = IBV_WR_RDMA_WRITE;
+ EXPECT_EQ(ENOSPC, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ request.opcode = IBV_WR_RDMA_READ;
+ EXPECT_EQ(ENOSPC, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ queries = test.queries;
+ request.opcode = IBV_WR_SEND;
+ request.wr.ru.ah = &explicit.ah_ex;
+ EXPECT_EQ(ENOSPC, cxi_post_send(&test.qp.vqp.qp, &request, &bad));
+ EXPECT_EQ(queries, test.queries);
+
+ cxi_wr_start(extended);
+ cxi_wr_send(extended);
+ cxi_wr_set_ru_addr(extended, NULL, 1);
+ EXPECT_EQ(0, test.qp.wr_build.err);
+ EXPECT_EQ(0, pthread_spin_trylock(&test.qp.sq_lock));
+ pthread_spin_unlock(&test.qp.sq_lock);
+ cxi_wr_send(extended);
+ EXPECT_EQ(1, test.qp.pending_count);
+ cxi_wr_abort(extended);
+ EXPECT_EQ(0, test.qp.pending_count);
+ EXPECT_EQ(0, test.qp.send_count);
+ cxi_wr_start(extended);
+ cxi_wr_rdma_read64(extended, 1, 0);
+ cxi_wr_set_ru_addr(extended, NULL, 1);
+ EXPECT_EQ(0, test.qp.wr_build.err);
+ EXPECT_EQ(ENOSPC, cxi_wr_complete(extended));
+ EXPECT_EQ(0, test.qp.send_count);
+ cxi_wr_start(extended);
+ cxi_wr_send(extended);
+ cxi_wr_set_ru_addr(extended, NULL, 2);
+ EXPECT_EQ(EINVAL, cxi_wr_complete(extended));
+
+ test_address_command_equivalence(&test);
+ memset(slot_valid, 0, 2048 * sizeof(*slot_valid));
+ mr.vmr.ibv_mr.lkey = 1;
+ test.context.mr_list = &mr;
+ pthread_spin_init(&test.context.mr_list_lock, PTHREAD_PROCESS_PRIVATE);
+ test.qp.sq_size = 2048;
+ test.qp.send_wrids = wrids;
+ test.qp.send_job_ids = job_ids;
+ test.qp.send_opcodes = opcodes;
+ test.qp.send_cookies = cookies;
+ test.qp.send_slot_valid = slot_valid;
+ cxi_cq_init(&test.qp.hw_txq, commands, 4096, &doorbell, 0);
+ test.check_lock = false;
+ ret = pthread_create(&updater, NULL, mock_update_addresses, &test);
+ EXPECT_EQ(0, ret);
+ if (!ret) {
+ for (size_t index = 0; index < ARRAY_SIZE(submitters); index++) {
+ ret = pthread_create(&submitters[index], NULL, mock_submit_indexed, &test);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ for (size_t previous = 0; previous < index; previous++)
+ pthread_join(submitters[previous], NULL);
+ break;
+ }
+ if (index + 1 == ARRAY_SIZE(submitters))
+ for (size_t thread = 0; thread < ARRAY_SIZE(submitters); thread++)
+ pthread_join(submitters[thread], NULL);
+ }
+ pthread_join(updater, NULL);
+ }
+ EXPECT_EQ(0, test.failures);
+ EXPECT_EQ(test.submitted, test.qp.send_count);
+ pthread_spin_destroy(&test.context.mr_list_lock);
+ free(test.qp.pending_wrs);
+ free(commands);
+ free(wrids);
+ free(cookies);
+ free(job_ids);
+ free(opcodes);
+ free(slot_valid);
+ pthread_spin_destroy(&test.qp.sq_lock);
+ pthread_mutex_destroy(&test.qp.builder_lock);
+ pthread_mutex_destroy(&test.lock);
+ correctness_result("indexed-submission-locking", before);
+}
+
+static void set_test_initiator_event(struct cxi_eq *eq, unsigned int index,
+ uint8_t event_type, uint64_t user_ptr)
+{
+ struct c_event_initiator_short *event =
+ (void *)(eq->events + C_EE_CFG_ECB_SIZE + index * sizeof(*event));
+
+ *event = (struct c_event_initiator_short){
+ .user_ptr = user_ptr,
+ .mlength = 64,
+ .event_size = C_EVENT_SIZE_16_BYTE,
+ .event_type = event_type,
+ .return_code = C_RC_OK,
+ };
+}
+
+static void test_out_of_order_send_completions(void)
+{
+ struct cxi_context context = {};
+ struct cxi_qp qp = {};
+ struct cxi_qp *cookie_qp;
+ struct cxi_ibv_cq cq = {};
+ struct cxi_eq eq = {
+ .byte_size = 256,
+ .rd_offset = C_EE_CFG_ECB_SIZE,
+ .prev_rd_offset = C_EE_CFG_ECB_SIZE,
+ };
+ union {
+ uint64_t alignment;
+ uint8_t bytes[256];
+ } event_buffer = {};
+ uint64_t wrids[] = { 100, 101, 102 };
+ uint64_t cookies[ARRAY_SIZE(wrids)] = {};
+ uint32_t job_ids[] = { 10, 11, 12 };
+ uint8_t opcodes[] = { IBV_WR_SEND, IBV_WR_RDMA_WRITE, IBV_WR_RDMA_READ };
+ bool slot_valid[] = { true, true, true };
+ struct ibv_wc wc = {};
+ uint64_t eq_state = 0;
+ uint64_t stale_cookie = 0;
+ uint64_t replacement_cookie = 0;
+ uint32_t free_slot = 2;
+ uint32_t cookie_slot = UINT32_MAX;
+ int before = failed_tests;
+ int ret;
+
+ eq.events = event_buffer.bytes;
+ eq.sw_state_addr = &eq_state;
+ cq.hw_eq = eq;
+ context.qp_slots[0] = &qp;
+ context.qp_by_pte[0] = &qp;
+ context.qp_count = 1;
+ qp.qp_slot = 0;
+ qp.pte_index = 1;
+ qp.vqp.qp.context = &context.verbs_ctx.context;
+ qp.vqp.qp.qp_num = 0x1234;
+ qp.send_cq = &cq;
+ qp.send_wrids = wrids;
+ qp.send_opcodes = opcodes;
+ qp.send_job_ids = job_ids;
+ qp.send_cookies = cookies;
+ qp.send_slot_valid = slot_valid;
+ qp.sq_size = ARRAY_SIZE(wrids);
+ qp.send_count = ARRAY_SIZE(wrids);
+ qp.job_id = 10;
+ cq.verbs_cq.cq.context = &context.verbs_ctx.context;
+
+ EXPECT_EQ(0, pthread_spin_init(&context.qp_table_lock, PTHREAD_PROCESS_PRIVATE));
+ EXPECT_EQ(0, pthread_spin_init(&qp.sq_lock, PTHREAD_PROCESS_PRIVATE));
+ EXPECT_EQ(0, pthread_spin_init(&cq.lock, PTHREAD_PROCESS_PRIVATE));
+ atomic_init(&context.send_cookie_seq, 0);
+ for (uint32_t slot = 0; slot < ARRAY_SIZE(wrids); slot++)
+ EXPECT_EQ(0, cxi_sq_make_cookie(&qp, slot, &cookies[slot]));
+ stale_cookie = cookies[2];
+ cookie_qp = cxi_sq_cookie_qp(&context, cookies[2], &cookie_slot);
+ EXPECT_EQ((uintptr_t)&qp, (uintptr_t)cookie_qp);
+ EXPECT_EQ(2, cookie_slot);
+ set_test_initiator_event(&cq.hw_eq, 0, C_EVENT_ACK, 0);
+ set_test_initiator_event(&cq.hw_eq, 1, C_EVENT_ACK, cookies[2]);
+ set_test_initiator_event(&cq.hw_eq, 2, C_EVENT_SEND, cookies[0]);
+ set_test_initiator_event(&cq.hw_eq, 3, C_EVENT_REPLY, cookies[1]);
+
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(1, ret);
+ EXPECT_EQ(102, wc.wr_id);
+ EXPECT_EQ(IBV_WC_RDMA_READ, wc.opcode);
+ EXPECT_EQ(IBV_WC_SUCCESS, wc.status);
+ EXPECT_EQ(0x1234, wc.qp_num);
+ EXPECT_EQ(12, cq.cur_job_id);
+ EXPECT_EQ(2, qp.send_count);
+ EXPECT_FALSE(slot_valid[2]);
+ EXPECT_TRUE(cxi_sq_find_free_slot(&qp, &free_slot));
+ EXPECT_EQ(2, free_slot);
+
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(1, ret);
+ EXPECT_EQ(100, wc.wr_id);
+ EXPECT_EQ(IBV_WC_SEND, wc.opcode);
+ EXPECT_EQ(10, cq.cur_job_id);
+ EXPECT_EQ(1, qp.send_count);
+
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(1, ret);
+ EXPECT_EQ(101, wc.wr_id);
+ EXPECT_EQ(IBV_WC_RDMA_WRITE, wc.opcode);
+ EXPECT_EQ(11, cq.cur_job_id);
+ EXPECT_EQ(0, qp.send_count);
+ EXPECT_FALSE(slot_valid[0]);
+ EXPECT_FALSE(slot_valid[1]);
+
+ /* Reuse the out-of-order hole and verify its new completion metadata. */
+ EXPECT_EQ(0, cxi_sq_make_cookie(&qp, free_slot, &replacement_cookie));
+ cxi_sq_record_send(&qp, free_slot, replacement_cookie, 202, IBV_WR_SEND, 22);
+ set_test_initiator_event(&cq.hw_eq, 4, C_EVENT_ACK, stale_cookie);
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(0, ret);
+ EXPECT_EQ(1, qp.send_count);
+ EXPECT_TRUE(slot_valid[2]);
+
+ set_test_initiator_event(&cq.hw_eq, 5, C_EVENT_ACK, replacement_cookie);
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(1, ret);
+ EXPECT_EQ(202, wc.wr_id);
+ EXPECT_EQ(22, cq.cur_job_id);
+ EXPECT_EQ(0, qp.send_count);
+
+ /* A completion for a free slot is consumed, never returned as a WR ID. */
+ set_test_initiator_event(&cq.hw_eq, 6, C_EVENT_ACK, replacement_cookie);
+ ret = cxi_poll_cq(&cq.verbs_cq.cq, 1, &wc);
+ EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, qp.send_count);
+
+ pthread_spin_destroy(&qp.sq_lock);
+ pthread_spin_destroy(&context.qp_table_lock);
+ pthread_spin_destroy(&cq.lock);
+ correctness_result("out-of-order-send-completions", before);
+}
+
+static void test_qp_lookup_indexes(void)
+{
+ struct cxi_context context = {};
+ struct cxi_qp qps[4] = {};
+ const int initial_ptes[] = { 20, 4, 11 };
+ int before = failed_tests;
+ int ret;
+
+ ret = pthread_spin_init(&context.qp_table_lock, PTHREAD_PROCESS_PRIVATE);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ return;
+
+ pthread_spin_lock(&context.qp_table_lock);
+ for (size_t index = 0; index < ARRAY_SIZE(initial_ptes); index++) {
+ qps[index].qp_slot = UINT32_MAX;
+ qps[index].pte_index = initial_ptes[index];
+ ret = cxi_qp_register_locked(&context, &qps[index]);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ goto out_unlock;
+ }
+ EXPECT_EQ(3, context.qp_count);
+ EXPECT_EQ((uintptr_t)&qps[1], (uintptr_t)context.qp_by_pte[0]);
+ EXPECT_EQ((uintptr_t)&qps[2], (uintptr_t)context.qp_by_pte[1]);
+ EXPECT_EQ((uintptr_t)&qps[0], (uintptr_t)context.qp_by_pte[2]);
+ EXPECT_EQ((uintptr_t)&qps[0], (uintptr_t)cxi_qp_find_pte_locked(&context, 20));
+ EXPECT_EQ((uintptr_t)&qps[1], (uintptr_t)cxi_qp_find_pte_locked(&context, 4));
+ EXPECT_EQ((uintptr_t)&qps[2], (uintptr_t)cxi_qp_find_pte_locked(&context, 11));
+ EXPECT_EQ(0, (uintptr_t)cxi_qp_find_pte_locked(&context, 12));
+
+ ret = cxi_qp_update_pte_locked(&context, &qps[1], 15);
+ EXPECT_EQ(0, ret);
+ EXPECT_EQ((uintptr_t)&qps[2], (uintptr_t)context.qp_by_pte[0]);
+ EXPECT_EQ((uintptr_t)&qps[1], (uintptr_t)context.qp_by_pte[1]);
+ EXPECT_EQ((uintptr_t)&qps[0], (uintptr_t)context.qp_by_pte[2]);
+ ret = cxi_qp_update_pte_locked(&context, &qps[1], 20);
+ EXPECT_EQ(EEXIST, ret);
+ EXPECT_EQ(15, qps[1].pte_index);
+ EXPECT_EQ((uintptr_t)&qps[1], (uintptr_t)cxi_qp_find_pte_locked(&context, 15));
+
+ cxi_qp_unregister_locked(&context, &qps[1]);
+ EXPECT_EQ(2, context.qp_count);
+ EXPECT_EQ(0, (uintptr_t)context.qp_slots[1]);
+ EXPECT_EQ(0, (uintptr_t)cxi_qp_find_pte_locked(&context, 15));
+ qps[3].qp_slot = UINT32_MAX;
+ qps[3].pte_index = 6;
+ ret = cxi_qp_register_locked(&context, &qps[3]);
+ EXPECT_EQ(0, ret);
+ EXPECT_EQ(1, qps[3].qp_slot);
+ EXPECT_EQ((uintptr_t)&qps[3], (uintptr_t)context.qp_slots[1]);
+ EXPECT_EQ((uintptr_t)&qps[3], (uintptr_t)context.qp_by_pte[0]);
+
+ cxi_qp_unregister_locked(&context, &qps[0]);
+ cxi_qp_unregister_locked(&context, &qps[2]);
+ cxi_qp_unregister_locked(&context, &qps[3]);
+ EXPECT_EQ(0, context.qp_count);
+
+out_unlock:
+ pthread_spin_unlock(&context.qp_table_lock);
+ pthread_spin_destroy(&context.qp_table_lock);
+ correctness_result("qp-lookup-indexes", before);
+}
+
+enum op { OP_PUT, OP_GET };
+
+/*
+ * How a matching PTE selects a priority-list entry: the incoming match bits
+ * must equal the entry's outside ignore_bits, the initiator must fall within
+ * the entry's match_id, where an all-ones PID or endpoint field is a
+ * wildcard, and the entry must permit the operation.
+ */
+static bool le_selects(const struct c_target_cmd *le, uint64_t match_bits, uint32_t initiator,
+ enum op op)
+{
+ const unsigned int bits = C_DFA_PID_BITS_MAX;
+ uint32_t le_pid = CXI_MATCH_ID_PID(bits, le->match_id);
+ uint32_t le_ep = CXI_MATCH_ID_EP(bits, le->match_id);
+
+ if ((match_bits ^ le->match_bits) & ~le->ignore_bits)
+ return false;
+ if (le_pid != C_PID_ANY && le_pid != CXI_MATCH_ID_PID(bits, initiator))
+ return false;
+ if (le_ep != C_RANK_ANY && le_ep != CXI_MATCH_ID_EP(bits, initiator))
+ return false;
+ return op == OP_GET ? le->op_get : le->op_put;
+}
+
+/* Includes the initiator a live two-node SEND actually carried. */
+static const uint32_t initiators[] = {
+ 0x00000000,
+ 0x00000001,
+ 0x36080000,
+ 0x00000807,
+ 0x00000836,
+ CXI_MATCH_ID(C_DFA_PID_BITS_MAX, 10, 0x807),
+ CXI_MATCH_ID(C_DFA_PID_BITS_MAX, 511, 0xfffff),
+ 0xffffffff,
+};
+
+#define NUM_INITIATORS ARRAY_SIZE(initiators)
+
+static const uint64_t rkeys[] = {
+ 0x1, 0x1234, 0xabcd0001, 0x00000001deadbeefULL, 0x8000000000000001ULL,
+};
+
+#define NUM_RKEYS ARRAY_SIZE(rkeys)
+
+static void test_model_enforces_match_id(void)
+{
+ union c_cmdu cmd;
+
+ /* A zeroed match_id admits only initiator 0; the model must say so. */
+ cxi_le_recv(&cmd, 10, 1, 0x1000, 4096);
+ cmd.target.match_id = 0;
+ EXPECT_FALSE(le_selects(&cmd.target, 0, 0x36080000, OP_PUT));
+}
+
+static void test_vni_key_encoding(void)
+{
+ union c_cmdu cmd;
+ uint64_t wire_key = 0;
+ const uint64_t keys[] = { 0, 1, 0xffffffffULL, CXI_LE_RKEY_MAX };
+
+ for (size_t index = 0; index < ARRAY_SIZE(keys); index++) {
+ cxi_le_recv(&cmd, 10, 1, 0x1000, 4096);
+ EXPECT_EQ(0, cxi_le_wire_key(keys[index], &wire_key));
+ EXPECT_EQ(keys[index], wire_key >> 16);
+ EXPECT_EQ(0, wire_key & CXI_LE_VNI_MASK);
+ EXPECT_EQ(0, cxi_le_set_vni(&cmd, keys[index], 0x7e01));
+ EXPECT_TRUE(le_selects(&cmd.target, wire_key | 0x7e01, 0, OP_PUT));
+ EXPECT_FALSE(le_selects(&cmd.target, wire_key | 0x7e00, 0, OP_PUT));
+ }
+ EXPECT_EQ(-EOPNOTSUPP, cxi_le_wire_key(1ULL << 48, &wire_key));
+ EXPECT_EQ(-EOPNOTSUPP, cxi_le_wire_key(UINT64_MAX, &wire_key));
+}
+
+static void test_recv_fields(void)
+{
+ union c_cmdu cmd;
+
+ cxi_le_recv(&cmd, 10, 5, 0x7f0000001000ULL, 4096);
+ EXPECT_EQ(C_CMD_TGT_APPEND, cmd.command.opcode);
+ EXPECT_EQ(10, cmd.target.ptlte_index);
+ EXPECT_EQ(C_PTL_LIST_PRIORITY, cmd.target.ptl_list);
+ EXPECT_EQ(5, cmd.target.lac);
+ EXPECT_EQ(0x7f0000001000ULL, cmd.target.start);
+ EXPECT_EQ(4096, cmd.target.length);
+ EXPECT_EQ(CXI_MATCH_ID_ANY, cmd.target.match_id);
+ EXPECT_EQ(CXI_LE_RECV_MATCH_BITS, cmd.target.match_bits);
+ EXPECT_EQ(0, cmd.target.ignore_bits);
+ EXPECT_EQ(1, cmd.target.op_put);
+ EXPECT_EQ(0, cmd.target.op_get);
+ EXPECT_EQ(1, cmd.target.use_once);
+}
+
+static void test_recv_selection(void)
+{
+ union c_cmdu cmd;
+ size_t i, k;
+
+ cxi_le_recv(&cmd, 10, 1, 0x1000, 4096);
+ for (i = 0; i < NUM_INITIATORS; i++) {
+ EXPECT_TRUE(le_selects(&cmd.target, 0, initiators[i], OP_PUT));
+ EXPECT_FALSE(le_selects(&cmd.target, 0, initiators[i], OP_GET));
+ for (k = 0; k < NUM_RKEYS; k++)
+ EXPECT_FALSE(le_selects(&cmd.target, rkeys[k], initiators[i], OP_PUT));
+ }
+}
+
+static void test_region_fields(void)
+{
+ union c_cmdu cmd;
+ size_t k;
+
+ for (k = 0; k < NUM_RKEYS; k++) {
+ cxi_le_region(&cmd, 12, 0xabc0 | 6, rkeys[k], 0x2000, 8192);
+ EXPECT_EQ(C_CMD_TGT_APPEND, cmd.command.opcode);
+ EXPECT_EQ(12, cmd.target.ptlte_index);
+ EXPECT_EQ(C_PTL_LIST_PRIORITY, cmd.target.ptl_list);
+ EXPECT_EQ(6, cmd.target.lac);
+ EXPECT_EQ(0x2000, cmd.target.start);
+ EXPECT_EQ(8192, cmd.target.length);
+ EXPECT_EQ(CXI_MATCH_ID_ANY, cmd.target.match_id);
+ EXPECT_EQ(rkeys[k], cmd.target.match_bits);
+ EXPECT_EQ(0, cmd.target.ignore_bits);
+ EXPECT_EQ(1, cmd.target.op_put);
+ EXPECT_EQ(1, cmd.target.op_get);
+ EXPECT_EQ(0, cmd.target.use_once);
+ EXPECT_EQ(rkeys[k] & 0xffff, cmd.target.buffer_id);
+ }
+}
+
+static void test_region_selection(void)
+{
+ union c_cmdu cmd;
+ size_t i, k, j;
+
+ for (k = 0; k < NUM_RKEYS; k++) {
+ cxi_le_region(&cmd, 12, 0, rkeys[k], 0x2000, 8192);
+ for (i = 0; i < NUM_INITIATORS; i++) {
+ uint32_t init = initiators[i];
+ uint64_t wrong_rkey = rkeys[k] ^ (1ULL << 63);
+
+ EXPECT_TRUE(le_selects(&cmd.target, rkeys[k], init, OP_PUT));
+ EXPECT_TRUE(le_selects(&cmd.target, rkeys[k], init, OP_GET));
+ /* A SEND must never land in a region. */
+ EXPECT_FALSE(le_selects(&cmd.target, 0, init, OP_PUT));
+ /* Exact: a key differing only in its high half is another key. */
+ EXPECT_FALSE(le_selects(&cmd.target, wrong_rkey, init, OP_PUT));
+ for (j = 0; j < NUM_RKEYS; j++) {
+ if (j == k)
+ continue;
+ EXPECT_FALSE(le_selects(&cmd.target, rkeys[j], init, OP_PUT));
+ }
+ }
+ }
+}
+
+static void test_region_unlink(void)
+{
+ union c_cmdu append, unlink;
+ size_t k;
+
+ for (k = 0; k < NUM_RKEYS; k++) {
+ cxi_le_region(&append, 12, 0, rkeys[k], 0x2000, 8192);
+ cxi_le_region_unlink(&unlink, 12, rkeys[k]);
+ EXPECT_EQ(C_CMD_TGT_UNLINK, unlink.command.opcode);
+ EXPECT_EQ(12, unlink.target.ptlte_index);
+ EXPECT_EQ(C_PTL_LIST_PRIORITY, unlink.target.ptl_list);
+ EXPECT_EQ(append.target.buffer_id, unlink.target.buffer_id);
+ }
+}
+
+/* The provider's create-QP response layout is ABI shared with the kernel. */
+static void test_create_qp_resp_abi(void)
+{
+ EXPECT_EQ(1, CXI_IB_UVERBS_ABI_VERSION);
+ EXPECT_EQ(88, sizeof(struct cxi_ibv_create_qp_resp));
+ EXPECT_EQ(72, offsetof(struct cxi_ibv_create_qp_resp, tgq_idx));
+ EXPECT_EQ(76, offsetof(struct cxi_ibv_create_qp_resp, job_id));
+ EXPECT_EQ(80, offsetof(struct cxi_ibv_create_qp_resp, pte_index));
+}
+
+static bool resource_ok(const char *operation, int ret)
+{
+ if (!ret)
+ return true;
+ fprintf(stderr, "%s: %s\n", operation, strerror(ret));
+ failed_tests++;
+ return false;
+}
+
+static bool test_exact_vni_service(struct ibv_context *context, const uint16_t *vnis,
+ uint32_t vni_count)
+{
+ struct cxidv_svc_attr config = {
+ .vni_count = vni_count,
+ .tc_mask = 1U << CXIDV_TC_BEST_EFFORT,
+ .flags = CXIDV_SVC_CREATE_TC,
+ .uid = geteuid(),
+ };
+ struct cxidv_svc_attr queried;
+ uint32_t svc_id = 0;
+ uint32_t index;
+ int before = failed_tests;
+
+ if (!vnis || !vni_count || vni_count > CXIDV_SVC_MAX_VNIS)
+ return false;
+ memcpy(config.vnis, vnis, vni_count * sizeof(config.vnis[0]));
+ if (!resource_ok("allocate exact-VNI service", cxidv_alloc_svc(context, &config, &svc_id)))
+ return false;
+ if (!resource_ok("query exact-VNI service", cxidv_query_svc(context, svc_id, &queried)))
+ goto out;
+ if (queried.vni_count != config.vni_count || queried.is_range || queried.is_system_svc ||
+ queried.vni_min || queried.vni_max) {
+ fprintf(stderr, "exact-VNI service query changed the granted set\n");
+ failed_tests++;
+ goto out;
+ }
+ for (index = 0; index < vni_count; index++) {
+ if (queried.vnis[index] != config.vnis[index]) {
+ fprintf(stderr, "exact-VNI service query changed the granted set\n");
+ failed_tests++;
+ goto out;
+ }
+ }
+ if (!resource_ok("destroy exact-VNI service", cxidv_destroy_svc(context, svc_id)))
+ return false;
+ return failed_tests == before;
+out:
+ if (svc_id)
+ resource_ok("cleanup exact-VNI service", cxidv_destroy_svc(context, svc_id));
+ return false;
+}
+
+static bool test_retry_system_service(struct ibv_context *context)
+{
+ struct cxidv_svc_attr config = {
+ .is_system_svc = 1,
+ .flags = CXIDV_SVC_LIMITS,
+ .uid = geteuid(),
+ .maximum = {
+ [CXIDV_SVC_EQS] = 1,
+ [CXIDV_SVC_ACS] = 1,
+ },
+ .reserved = {
+ [CXIDV_SVC_EQS] = 1,
+ [CXIDV_SVC_ACS] = 1,
+ },
+ };
+ struct cxidv_svc_attr queried;
+ uint32_t svc_id = 0;
+ uint32_t lpr;
+ int before = failed_tests;
+
+ if (!resource_ok("allocate retry system service",
+ cxidv_alloc_svc(context, &config, &svc_id)))
+ return false;
+ if (!resource_ok("query retry system service", cxidv_query_svc(context, svc_id, &queried)))
+ goto out;
+ if (!queried.is_system_svc || queried.is_range || queried.vni_count || queried.vni_min ||
+ queried.vni_max || queried.tc_mask || queried.vnis[0] || queried.vnis[1] ||
+ queried.vnis[2] || queried.vnis[3]) {
+ fprintf(stderr, "retry system service query changed restricted-empty policy\n");
+ failed_tests++;
+ goto out;
+ }
+ if (!resource_ok("query retry system service LPR",
+ cxidv_get_svc_lpr(context, svc_id, &lpr)))
+ goto out;
+ if (lpr != 1) {
+ fprintf(stderr, "retry system service LPR is %u, expected 1\n", lpr);
+ failed_tests++;
+ goto out;
+ }
+ if (!resource_ok("destroy retry system service", cxidv_destroy_svc(context, svc_id)))
+ return false;
+ return failed_tests == before;
+out:
+ if (svc_id)
+ resource_ok("cleanup retry system service", cxidv_destroy_svc(context, svc_id));
+ return false;
+}
+
+static void test_hardware_resources(const char *name)
+{
+ static const uint16_t one_vni[] = { 0x7e00 };
+ static const uint16_t two_vnis[] = { 0x7e00, 0x7e03 };
+ static const uint16_t four_vnis[] = { 0x7e00, 0x7e03, 0x7e07, 0x7e0b };
+ struct ibv_device **devices = ibv_get_device_list(NULL);
+ struct ibv_context *context = NULL;
+ struct ibv_context *bare = NULL;
+ struct cxidv_lni *lni = NULL;
+ struct ibv_pd *first = NULL;
+ struct ibv_pd *second = NULL;
+ struct ibv_mr *mr = NULL;
+ struct ibv_mr *receive_mr = NULL;
+ struct ibv_ah *destination = NULL;
+ struct ibv_cq *cq = NULL;
+ struct ibv_job *job = NULL;
+ struct ibv_job *alternate_job = NULL;
+ struct ibv_job *retired_job = NULL;
+ struct ibv_job_key *first_key = NULL;
+ struct ibv_job_key *second_key = NULL;
+ struct ibv_job_key *alternate_key = NULL;
+ struct ibv_job_key *retired_grant = NULL;
+ struct ibv_qp *first_qp = NULL;
+ struct ibv_qp *second_qp = NULL;
+ struct cxidv_svc_attr config = {
+ .is_range = 1,
+ .vnis = { 0x7e00, 0x7e03 },
+ .tc_mask = 1U << CXIDV_TC_BEST_EFFORT,
+ .flags = CXIDV_SVC_CREATE_TC,
+ };
+ struct cxidv_svc_attr queried;
+ struct ibv_device_attr device_attr;
+ uint32_t svc_id = 0;
+ uint32_t first_pid, second_pid, queried_pid;
+ char buffer[4096];
+ char receive_buffer[4096];
+ int default_enabled = 0;
+ bool restore_default = false;
+ bool indexed = getenv("CXI_TEST_INDEXED");
+ int index;
+ int ret;
+
+ if (!devices) {
+ failed_tests++;
+ return;
+ }
+ for (index = 0; devices[index]; index++)
+ if (!strcmp(ibv_get_device_name(devices[index]), name))
+ break;
+ if (!devices[index]) {
+ fprintf(stderr, "missing device %s\n", name);
+ failed_tests++;
+ goto out;
+ }
+ context = ibv_open_device(devices[index]);
+ if (!context) {
+ resource_ok("open device", errno);
+ goto out;
+ }
+ config.uid = geteuid();
+ if (!test_exact_vni_service(context, one_vni, ARRAY_SIZE(one_vni)) ||
+ !test_exact_vni_service(context, two_vnis, ARRAY_SIZE(two_vnis)) ||
+ !test_exact_vni_service(context, four_vnis, ARRAY_SIZE(four_vnis)) ||
+ !test_retry_system_service(context))
+ goto out;
+ if (!resource_ok("allocate service", cxidv_alloc_svc(context, &config, &svc_id)))
+ goto out;
+ if (!resource_ok("query service", cxidv_query_svc(context, svc_id, &queried)))
+ goto out;
+ EXPECT_EQ(0, queried.vni_count);
+ EXPECT_EQ(1, queried.is_range);
+ EXPECT_EQ(0, queried.vni_min);
+ EXPECT_EQ(0, queried.vni_max);
+ EXPECT_EQ(config.vnis[0], queried.vnis[0]);
+ EXPECT_EQ(config.vnis[1], queried.vnis[1]);
+ EXPECT_EQ(config.tc_mask, queried.tc_mask);
+ if (!resource_ok("query default service state",
+ cxidv_svc_is_enabled(context, 1, &default_enabled)))
+ goto out;
+ if (!resource_ok("disable default service", cxidv_enable_svc(context, 1, 0)))
+ goto out;
+ restore_default = true;
+ bare = ibv_open_device(devices[index]);
+ if (!bare) {
+ resource_ok("open without default service", errno);
+ goto out;
+ }
+ if (!resource_ok("query without default service", ibv_query_device(bare, &device_attr)))
+ goto out;
+ first = ibv_alloc_pd(bare);
+ EXPECT_FALSE(first);
+ if (first)
+ goto out;
+ if (!resource_ok("allocate explicit LNI", cxidv_alloc_lni(bare, svc_id, &lni)))
+ goto out;
+ first = cxidv_alloc_pd(lni);
+ second = cxidv_alloc_pd(lni);
+ if (!first || !second) {
+ resource_ok("allocate explicit PDs", errno);
+ goto out;
+ }
+ if (!resource_ok("allocate first PID", cxidv_alloc_pid(first, 0, &first_pid)) ||
+ !resource_ok("allocate second PID", cxidv_alloc_pid(second, 0, &second_pid)))
+ goto out;
+ EXPECT_TRUE(first_pid != second_pid);
+ if (!resource_ok("query first PID", cxidv_query_pid(first, &queried_pid)))
+ goto out;
+ EXPECT_EQ(first_pid, queried_pid);
+ EXPECT_EQ(EBUSY, cxidv_alloc_pid(first, 0, &queried_pid));
+ ret = cxidv_destroy_lni(lni);
+ EXPECT_EQ(EBUSY, ret);
+ if (!ret) {
+ lni = NULL;
+ goto out;
+ }
+ mr = ibv_reg_mr(first, buffer, sizeof(buffer), IBV_ACCESS_LOCAL_WRITE);
+ if (!mr) {
+ resource_ok("register memory", errno);
+ goto out;
+ }
+ EXPECT_EQ((uint32_t)mr->lkey64, mr->lkey);
+ EXPECT_TRUE(mr->lkey != 0);
+ {
+ struct cxidv_mr_info info;
+
+ if (!resource_ok("query native memory descriptor", cxidv_mr_from_ibv_mr(mr, &info)))
+ goto out;
+ EXPECT_TRUE(info.va <= (uintptr_t)buffer);
+ EXPECT_TRUE(info.length >= sizeof(buffer));
+ EXPECT_TRUE((uintptr_t)buffer - info.va <= info.length - sizeof(buffer));
+ EXPECT_EQ(mr->lkey64 & CXI_LKEY64_LAC_MASK, info.lac);
+ EXPECT_EQ((mr->lkey64 >> CXI_LKEY64_MD_SHIFT) & CXI_LKEY64_MD_MASK, info.id);
+ EXPECT_EQ(to_cxi_mr(mr)->iova, info.iova + (uintptr_t)buffer - info.va);
+ EXPECT_TRUE(info.page_shift > 0);
+ }
+ {
+ struct ibv_mr_init_attr zero_key_attr = {
+ .comp_mask = IBV_REG_MR_MASK_ADDR | IBV_REG_MR_MASK_RKEY,
+ .addr = buffer,
+ .length = sizeof(buffer),
+ };
+ struct ibv_mr *zero_key_mr;
+
+ errno = 0;
+ zero_key_mr = ibv_reg_mr_ex(first, &zero_key_attr);
+ EXPECT_FALSE(zero_key_mr);
+ EXPECT_EQ(EINVAL, errno);
+ if (zero_key_mr)
+ resource_ok("deregister invalid zero-key MR", ibv_dereg_mr(zero_key_mr));
+ }
+ cq = ibv_create_cq(bare, 64, NULL, NULL, 0);
+ if (!cq) {
+ resource_ok("create CQ", errno);
+ goto out;
+ }
+ {
+ struct ibv_job_attr attr = {
+ .comp_mask =
+ IBV_JOB_ATTR_ID | IBV_JOB_ATTR_PORT_NUM | IBV_JOB_ATTR_SGID_INDEX,
+ .id = config.vnis[0],
+ .port_num = 1,
+ };
+ struct ibv_qp_init_attr_ex qp_attr = {
+ .send_cq = cq,
+ .recv_cq = cq,
+ .cap = {
+ .max_send_wr = 16,
+ .max_recv_wr = 16,
+ .max_send_sge = 1,
+ .max_recv_sge = 1,
+ },
+ .qp_type = IBV_QPT_RU,
+ .comp_mask = IBV_QP_INIT_ATTR_PD |
+ IBV_QP_INIT_ATTR_JKEY | IBV_QP_INIT_ATTR_CREATE_FLAGS |
+ IBV_QP_INIT_ATTR_SEND_OPS_FLAGS,
+ .create_flags = IBV_QP_CREATE_SOURCE_QPN,
+ .send_ops_flags = IBV_QP_EX_WITH_SEND,
+ };
+ struct ibv_qp *duplicate;
+ struct ibv_job *imported = NULL;
+ struct ibv_job_attr queried_job = {};
+ int job_fd = -1;
+
+ if (indexed) {
+ attr.comp_mask |= IBV_JOB_ATTR_MAX_ADDR_ENTRIES;
+ attr.max_addr_entries = 2;
+ }
+ job = ibv_alloc_job(bare, &attr, NULL);
+ if (!job) {
+ resource_ok("allocate generic job", errno);
+ goto out;
+ }
+ if (!resource_ok("query job", ibv_query_job(job, &queried_job)))
+ goto out;
+ EXPECT_EQ(config.vnis[0], queried_job.id);
+ EXPECT_EQ(indexed ? 2 : 0, queried_job.max_addr_entries);
+ if (!indexed) {
+ struct ibv_ah_attr_ex address = {};
+
+ EXPECT_EQ(EINVAL, ibv_insert_addr(job, &address, 0));
+ EXPECT_EQ(EINVAL, ibv_query_addr(job, 0, &address));
+ EXPECT_EQ(EINVAL, ibv_remove_addr(job, 0));
+ }
+ if (!resource_ok("export job", ibv_export_job(job, &job_fd)))
+ goto out;
+ ret = ibv_import_job(context, job_fd, &imported);
+ close(job_fd);
+ if (!resource_ok("import job into bare context", ret))
+ goto out;
+ resource_ok("query imported job", ibv_query_job(imported, &queried_job));
+ EXPECT_EQ(config.vnis[0], queried_job.id);
+ resource_ok("close imported job", ibv_dealloc_job(imported));
+ attr.id++;
+ attr.comp_mask &= ~IBV_JOB_ATTR_MAX_ADDR_ENTRIES;
+ attr.max_addr_entries = 0;
+ alternate_job = ibv_alloc_job(bare, &attr, NULL);
+ if (!alternate_job) {
+ resource_ok("allocate alternate job", errno);
+ goto out;
+ }
+ first_key = ibv_create_jkey(first, job);
+ second_key = ibv_create_jkey(second, alternate_job);
+ alternate_key = ibv_create_jkey(first, alternate_job);
+ if (!first_key || !second_key || !alternate_key) {
+ resource_ok("create job keys", errno);
+ goto out;
+ }
+ errno = 0;
+ EXPECT_EQ(-1, ibv_dealloc_job(alternate_job));
+ EXPECT_EQ(EBUSY, errno);
+ qp_attr.pd = first;
+ qp_attr.job_key = first_key;
+ qp_attr.source_qpn = first_pid | (7U << IBV_QPN_UET_RI_SHIFT);
+ {
+ struct ibv_qp_attr initial_attr = {
+ .path_mtu = IBV_MTU_2048,
+ };
+
+ qp_attr.comp_mask |= IBV_QP_INIT_ATTR_QP_ATTR;
+ qp_attr.qp_attr = &initial_attr;
+ qp_attr.qp_attr_mask = IBV_QP_PATH_MTU;
+ errno = 0;
+ duplicate = ibv_create_qp_ex(bare, &qp_attr);
+ EXPECT_FALSE(duplicate);
+ EXPECT_EQ(EOPNOTSUPP, errno);
+ if (duplicate)
+ resource_ok("destroy unsupported MTU QP",
+ ibv_destroy_qp(duplicate));
+ qp_attr.comp_mask &= ~IBV_QP_INIT_ATTR_QP_ATTR;
+ qp_attr.qp_attr = NULL;
+ qp_attr.qp_attr_mask = 0;
+ }
+ first_qp = ibv_create_qp_ex(bare, &qp_attr);
+ if (!first_qp) {
+ resource_ok("create first QP", errno);
+ goto out;
+ }
+ EXPECT_EQ(qp_attr.source_qpn, first_qp->qp_num);
+ EXPECT_EQ(IBV_QPS_RTS, first_qp->state);
+ {
+ struct ibv_qp_attr queried_qp = {};
+ struct ibv_qp_init_attr initialized = {};
+ struct ibv_qp_attr invalid = {
+ .qp_state = IBV_QPS_INIT,
+ };
+
+ if (resource_ok("query kernel RTS state",
+ ibv_query_qp(first_qp, &queried_qp,
+ IBV_QP_STATE | IBV_QP_PATH_MTU,
+ &initialized))) {
+ EXPECT_EQ(IBV_QPS_RTS, queried_qp.qp_state);
+ EXPECT_EQ(IBV_MTU_4096, queried_qp.path_mtu);
+ }
+ EXPECT_EQ(EINVAL, ibv_modify_qp(first_qp, &invalid, IBV_QP_STATE));
+ }
+ {
+ struct ibv_qp_ex *extended = ibv_qp_to_qp_ex(first_qp);
+ struct cxi_qp *provider_qp = to_cxi_qp(first_qp);
+
+ EXPECT_TRUE(extended);
+ if (!extended)
+ goto out;
+ ibv_wr_start(extended);
+ ibv_wr_set_job_key(extended, first_key->jkey);
+ EXPECT_EQ(EINVAL, ibv_wr_complete(extended));
+ ibv_wr_start(extended);
+ ibv_wr_send(extended);
+ ibv_wr_set_job_key(extended, UINT32_MAX);
+ EXPECT_EQ(EINVAL, provider_qp->wr_build.err);
+ EXPECT_EQ(EINVAL, ibv_wr_complete(extended));
+ ibv_wr_start(extended);
+ ibv_wr_send(extended);
+ ibv_wr_set_job_key(extended, first_key->jkey);
+ EXPECT_EQ(0, provider_qp->wr_build.err);
+ ibv_wr_abort(extended);
+ }
+ duplicate = ibv_create_qp_ex(bare, &qp_attr);
+ EXPECT_FALSE(duplicate);
+ if (duplicate)
+ resource_ok("destroy invalid duplicate", ibv_destroy_qp(duplicate));
+ qp_attr.pd = second;
+ qp_attr.job_key = second_key;
+ qp_attr.source_qpn = second_pid | (7U << IBV_QPN_UET_RI_SHIFT);
+ second_qp = ibv_create_qp_ex(bare, &qp_attr);
+ if (!second_qp) {
+ resource_ok("create second QP with same SI", errno);
+ goto out;
+ }
+ EXPECT_TRUE(first_qp->qp_num != second_qp->qp_num);
+ receive_mr = ibv_reg_mr(second, receive_buffer, sizeof(receive_buffer),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (!receive_mr) {
+ resource_ok("register alternate-job receive memory", errno);
+ goto out;
+ }
+ for (int pass = 0; pass < (getenv("CXI_TEST_WRONG_VNI") ? 4 : 2); pass++) {
+ int before = failed_tests;
+ uint64_t receive_id = pass == 3 ? 102 : 100 + pass;
+ uint32_t expected_job = config.vnis[0] + (pass == 1 || pass == 2 ? 0 : 1);
+ struct ibv_qp *target = pass == 1 ? first_qp : second_qp;
+ struct ibv_mr *target_mr = pass == 1 ? mr : receive_mr;
+ struct ibv_ah_attr_ex address = {
+ .ah_attr = {
+ .is_global = 1,
+ .port_num = 1,
+ .grh.hop_limit = 1,
+ },
+ .remote_qpn = target->qp_num,
+ };
+ struct ibv_sge receive = {
+ .addr = (uintptr_t)(pass == 1 ? buffer : receive_buffer),
+ .length = 64,
+ .lkey = target_mr->lkey,
+ };
+ struct ibv_recv_wr request = {
+ .wr_id = 100 + pass,
+ .sg_list = &receive,
+ .num_sge = 1,
+ };
+ struct ibv_recv_wr *bad;
+ struct ibv_qp_ex *extended = ibv_qp_to_qp_ex(first_qp);
+ struct timespec started, now;
+ bool sent = false, received = false;
+ bool send_rejected = false;
+
+ memset(buffer, 0x5a, 64);
+ memset(receive_buffer, 0, 64);
+ if (!resource_ok("query local GID",
+ ibv_query_gid(bare, 1, 0, &address.ah_attr.grh.dgid)))
+ goto out;
+ if (indexed) {
+ struct ibv_ah_attr_ex result = {};
+
+ EXPECT_EQ(ENOENT, ibv_query_addr(job, 1, &result));
+ EXPECT_EQ(ENOENT, ibv_remove_addr(job, 1));
+ EXPECT_EQ(EINVAL, ibv_insert_addr(job, &address, 2));
+ EXPECT_EQ(EINVAL, ibv_query_addr(job, UINT_MAX, &result));
+ EXPECT_EQ(EINVAL, ibv_remove_addr(job, 2));
+ if (!resource_ok("insert indexed address",
+ ibv_insert_addr(job, &address, 1)))
+ goto out;
+ EXPECT_EQ(EBUSY, ibv_insert_addr(job, &address, 1));
+ EXPECT_EQ(0, ibv_query_addr(job, 1, &result));
+ EXPECT_EQ(address.remote_qpn, result.remote_qpn);
+ EXPECT_EQ(0, memcmp(address.ah_attr.grh.dgid.raw,
+ result.ah_attr.grh.dgid.raw, 16));
+ EXPECT_EQ(0, ibv_remove_addr(job, 1));
+ EXPECT_EQ(ENOENT, ibv_query_addr(job, 1, &result));
+ if (!resource_ok("reinsert indexed address",
+ ibv_insert_addr(job, &address, 1)))
+ goto out;
+ }
+ destination = ibv_create_ah_ex(first, &address);
+ if (!destination) {
+ resource_ok("create loopback address", errno);
+ goto out;
+ }
+ if (pass != 3 && !resource_ok("post job-selected receive",
+ ibv_post_recv(target, &request, &bad)))
+ goto out;
+ ibv_wr_start(extended);
+ extended->wr_id = 200 + pass;
+ ibv_wr_send(extended);
+ if (!pass || pass == 3)
+ ibv_wr_set_job_key(extended, alternate_key->jkey);
+ ibv_wr_set_ru_addr(extended, indexed ? NULL : ibv_ah_to_ah_ex(destination),
+ indexed ? 1 : 0);
+ ibv_wr_set_sge(extended, mr->lkey, (uintptr_t)buffer, 64);
+ if (!resource_ok("send selected job", ibv_wr_complete(extended)))
+ goto out;
+ clock_gettime(CLOCK_MONOTONIC, &started);
+ do {
+ struct ibv_wc completion;
+
+ ret = ibv_poll_cq(cq, 1, &completion);
+ if (ret < 0) {
+ resource_ok("poll selected-job CQ", EIO);
+ goto out;
+ }
+ if (ret) {
+ if (completion.wr_id == (uint64_t)(200 + pass))
+ send_rejected = completion.status != IBV_WC_SUCCESS;
+ if (pass != 2)
+ EXPECT_EQ(IBV_WC_SUCCESS, completion.status);
+ if (completion.wr_id == receive_id ||
+ completion.wr_id == (uint64_t)(200 + pass)) {
+ EXPECT_EQ(expected_job,
+ to_cxi_ibv_cq(cq)->cur_job_id);
+ }
+ sent |= completion.wr_id == (uint64_t)(200 + pass);
+ received |= completion.wr_id == receive_id &&
+ completion.status == IBV_WC_SUCCESS;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ } while ((pass == 2 ? !sent : !(sent && received)) &&
+ now.tv_sec - started.tv_sec < 10);
+ if (pass == 2) {
+ EXPECT_TRUE(sent && send_rejected);
+ EXPECT_TRUE(!received);
+ if (received)
+ fprintf(stderr, "relative endpoint accepted wrong VNI\n");
+ correctness_result("unconfigured-job-receive", before);
+ } else {
+ EXPECT_TRUE(sent && received);
+ }
+ if (!pass || pass == 3)
+ EXPECT_EQ(0, memcmp(buffer, receive_buffer, 64));
+ if (!pass) {
+ correctness_result("multi-job-send", before);
+ correctness_result("multi-job-receive", before);
+ }
+ if (pass == 1)
+ correctness_result("default-job-restoration", before);
+ if (pass == 3)
+ correctness_result("receive-after-job-rejection", before);
+ if (pass != 2 && !(sent && received))
+ goto out;
+ if (!resource_ok("destroy loopback address", ibv_destroy_ah(destination)))
+ goto out;
+ destination = NULL;
+ if (indexed)
+ EXPECT_EQ(0, ibv_remove_addr(job, 1));
+ }
+ {
+ struct ibv_qp_ex *extended = ibv_qp_to_qp_ex(first_qp);
+ uint32_t retired_key = alternate_key->jkey;
+ uint64_t before = to_cxi_qp(first_qp)->hw_txq.wp32;
+ int failures_before = failed_tests;
+
+ if (!resource_ok("destroy alternate key", ibv_destroy_jkey(alternate_key)))
+ goto out;
+ alternate_key = NULL;
+ ibv_wr_start(extended);
+ ibv_wr_send(extended);
+ ibv_wr_set_job_key(extended, retired_key);
+ EXPECT_EQ(EINVAL, ibv_wr_complete(extended));
+ EXPECT_EQ(before, to_cxi_qp(first_qp)->hw_txq.wp32);
+ correctness_result("retired-job-grant-send", failures_before);
+ }
+ {
+ struct ibv_job_attr retired_attr = attr;
+ struct ibv_qp_ex *extended = ibv_qp_to_qp_ex(first_qp);
+ uint32_t retired_value;
+ uint64_t before = to_cxi_qp(first_qp)->hw_txq.wp32;
+ int failures_before = failed_tests;
+
+ retired_attr.id = config.vnis[0] + 2;
+ retired_job = ibv_alloc_job(bare, &retired_attr, NULL);
+ if (!retired_job) {
+ resource_ok("allocate retirement-test job", errno);
+ goto out;
+ }
+ retired_grant = ibv_create_jkey(first, retired_job);
+ if (!retired_grant) {
+ resource_ok("create retirement-test grant", errno);
+ goto out;
+ }
+ retired_value = retired_grant->jkey;
+ if (!resource_ok("destroy last job grant", ibv_destroy_jkey(retired_grant)))
+ goto out;
+ retired_grant = NULL;
+ if (!resource_ok("deallocate unreferenced job",
+ ibv_dealloc_job(retired_job)))
+ goto out;
+ retired_job = NULL;
+ ibv_wr_start(extended);
+ ibv_wr_send(extended);
+ ibv_wr_set_job_key(extended, retired_value);
+ EXPECT_EQ(EINVAL, ibv_wr_complete(extended));
+ EXPECT_EQ(before, to_cxi_qp(first_qp)->hw_txq.wp32);
+ correctness_result("deallocated-job-send", failures_before);
+ }
+ {
+ struct ibv_sge64 sge = {
+ .addr = (uintptr_t)buffer,
+ .length = sizeof(buffer),
+ .lkey64 = mr->lkey64,
+ };
+ struct ibv_recv_wr64 wr = {
+ .wr_id = 7,
+ .sg_list = &sge,
+ .num_sge = 1,
+ };
+ struct ibv_recv_wr64 *bad = NULL;
+
+ resource_ok("post receive64", ibv_post_recv64(first_qp, &wr, &bad));
+ sge.lkey64 ^= 1ULL << 63;
+ EXPECT_EQ(EINVAL, ibv_post_recv64(first_qp, &wr, &bad));
+ EXPECT_TRUE(bad == &wr);
+ sge.lkey64 = mr->lkey64;
+ EXPECT_EQ(EINVAL, ibv_post_recv64(second_qp, &wr, &bad));
+ }
+ }
+out:
+ if (retired_grant)
+ resource_ok("destroy retirement-test grant", ibv_destroy_jkey(retired_grant));
+ if (retired_job)
+ resource_ok("destroy retirement-test job", ibv_dealloc_job(retired_job));
+ if (destination)
+ resource_ok("destroy loopback address", ibv_destroy_ah(destination));
+ if (second_qp)
+ resource_ok("destroy second QP", ibv_destroy_qp(second_qp));
+ if (first_qp)
+ resource_ok("destroy first QP", ibv_destroy_qp(first_qp));
+ if (second_key)
+ resource_ok("destroy second key", ibv_destroy_jkey(second_key));
+ if (first_key)
+ resource_ok("destroy first key", ibv_destroy_jkey(first_key));
+ if (alternate_key)
+ resource_ok("destroy alternate key", ibv_destroy_jkey(alternate_key));
+ if (alternate_job)
+ resource_ok("destroy alternate job", ibv_dealloc_job(alternate_job));
+ if (job)
+ resource_ok("destroy job", ibv_dealloc_job(job));
+ if (cq)
+ resource_ok("destroy CQ", ibv_destroy_cq(cq));
+ if (mr)
+ resource_ok("deregister memory", ibv_dereg_mr(mr));
+ if (receive_mr)
+ resource_ok("deregister receive memory", ibv_dereg_mr(receive_mr));
+ if (second)
+ resource_ok("destroy second PD", ibv_dealloc_pd(second));
+ if (first)
+ resource_ok("destroy first PD", ibv_dealloc_pd(first));
+ if (lni)
+ resource_ok("destroy LNI", cxidv_destroy_lni(lni));
+ if (bare)
+ resource_ok("close bare context", ibv_close_device(bare));
+ if (restore_default)
+ resource_ok("restore default service",
+ cxidv_enable_svc(context, 1, default_enabled));
+ if (svc_id)
+ resource_ok("destroy service", cxidv_destroy_svc(context, svc_id));
+ if (context)
+ resource_ok("close context", ibv_close_device(context));
+ ibv_free_device_list(devices);
+}
+
+static void test_csr_mapping(const char *device_name)
+{
+ struct ibv_device **devices = ibv_get_device_list(NULL);
+ struct ibv_context *context = NULL;
+ struct cxidv_csr *csr = NULL;
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned int iteration;
+ void *invalid;
+ uint64_t offset;
+ size_t length;
+ int index;
+ int ret;
+
+ EXPECT_TRUE(devices);
+ if (!devices)
+ return;
+ for (index = 0; devices[index]; index++) {
+ if (!strcmp(ibv_get_device_name(devices[index]), device_name)) {
+ context = ibv_open_device(devices[index]);
+ break;
+ }
+ }
+ EXPECT_TRUE(context);
+ if (!context)
+ goto out;
+ EXPECT_TRUE(page_size > 0);
+ if (page_size <= 0)
+ goto out;
+ EXPECT_EQ(EINVAL, cxidv_map_csrs(NULL, &csr));
+ EXPECT_EQ(EINVAL, cxidv_map_csrs(context, NULL));
+ EXPECT_EQ(EINVAL, cxidv_unmap_csrs(NULL));
+ if (geteuid() != 0) {
+ EXPECT_EQ(EPERM, cxidv_map_csrs(context, &csr));
+ EXPECT_FALSE(csr);
+ goto out;
+ }
+ for (iteration = 0; iteration < 16; iteration++) {
+ ret = cxidv_map_csrs(context, &csr);
+ if (!resource_ok("map CSR aperture", ret))
+ goto out;
+ EXPECT_TRUE(csr->address);
+ EXPECT_TRUE(page_size > 0 && csr->length >= (size_t)page_size);
+ EXPECT_EQ(0, csr->length % page_size);
+ length = csr->length;
+ offset = csr->mmap_offset;
+ errno = 0;
+ invalid = mmap(NULL, length + page_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ context->cmd_fd, offset);
+ EXPECT_TRUE(invalid == MAP_FAILED);
+ EXPECT_EQ(EINVAL, errno);
+ if (invalid != MAP_FAILED)
+ munmap(invalid, length + page_size);
+ ret = cxidv_unmap_csrs(csr);
+ if (!resource_ok("unmap CSR aperture", ret))
+ goto out;
+ csr = NULL;
+ errno = 0;
+ invalid = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, context->cmd_fd,
+ offset);
+ EXPECT_TRUE(invalid == MAP_FAILED);
+ EXPECT_EQ(EINVAL, errno);
+ if (invalid != MAP_FAILED)
+ munmap(invalid, length);
+ }
+out:
+ if (csr)
+ resource_ok("cleanup CSR aperture", cxidv_unmap_csrs(csr));
+ if (context)
+ resource_ok("close CSR context", ibv_close_device(context));
+ ibv_free_device_list(devices);
+}
+
+static void test_csr_accessors(void)
+{
+ uint64_t backing[4] = {};
+ uint64_t input[] = { 0x123456789abcdef0ULL, 0xfedcba9876543210ULL };
+ uint64_t output[2] = {};
+ unsigned char unaligned[sizeof(input) + 1];
+ struct cxidv_csr csr = {
+ .address = backing,
+ .length = sizeof(backing),
+ };
+
+ EXPECT_EQ(EINVAL, cxidv_read_csr(NULL, 0, output, sizeof(output)));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 0, NULL, sizeof(output)));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 0, output, 0));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 1, output, sizeof(output)));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 0, output, 1));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, UINT64_MAX, output, 8));
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 0, output, SIZE_MAX));
+ EXPECT_EQ(EINVAL, cxidv_write_csr(NULL, 0, input, sizeof(input)));
+ EXPECT_EQ(EINVAL, cxidv_write_csr(&csr, 0, NULL, sizeof(input)));
+ EXPECT_EQ(EINVAL, cxidv_write_csr(&csr, 1, input, sizeof(input)));
+ EXPECT_EQ(EINVAL, cxidv_write_csr(&csr, 24, input, sizeof(input)));
+ EXPECT_EQ(EINVAL, cxidv_write8_csr(NULL, 0, 1));
+ EXPECT_EQ(EINVAL, cxidv_write8_csr(&csr, sizeof(backing), 1));
+ EXPECT_EQ(0, backing[0]);
+ EXPECT_EQ(0, backing[3]);
+ memcpy(unaligned + 1, input, sizeof(input));
+ EXPECT_EQ(0, cxidv_write_csr(&csr, 8, unaligned + 1, sizeof(input)));
+ EXPECT_EQ(input[0], le64toh(backing[1]));
+ EXPECT_EQ(input[1], le64toh(backing[2]));
+ EXPECT_EQ(0, backing[0]);
+ EXPECT_EQ(0, backing[3]);
+ memset(unaligned, 0, sizeof(unaligned));
+ EXPECT_EQ(0, cxidv_read_csr(&csr, 8, unaligned + 1, sizeof(input)));
+ EXPECT_EQ(0, memcmp(unaligned + 1, input, sizeof(input)));
+ EXPECT_EQ(0, cxidv_write8_csr(&csr, 11, 0x5a));
+ EXPECT_EQ(0x5a, ((unsigned char *)backing)[11]);
+ EXPECT_EQ(0, ((unsigned char *)backing)[0]);
+ csr.address = NULL;
+ EXPECT_EQ(EINVAL, cxidv_read_csr(&csr, 0, output, sizeof(output)));
+ EXPECT_EQ(EINVAL, cxidv_write8_csr(&csr, 0, 1));
+}
+
+static void test_command_ring_bounds(void)
+{
+ struct {
+ struct cxi_cmd64 commands[16];
+ uint64_t canary;
+ } storage = {};
+ const unsigned int count = ARRAY_SIZE(storage.commands);
+ struct cxi_cq queue = {};
+ unsigned int size;
+ unsigned int position;
+ int before = failed_tests;
+
+ storage.canary = UINT64_C(0x123456789abcdef0);
+ for (size = 0; size < ARRAY_SIZE(__cxi_cmd_slots); size++) {
+ for (position = C_CQ_FIRST_WR_PTR_32; position < 2 * count; position++) {
+ cxi_cq_init(&queue, storage.commands, count, NULL, 0);
+ queue.wp32 = position;
+ queue.rp32 = position;
+ queue.status->rd_ptr = position / 2;
+ EXPECT_EQ(0, cxi_cq_align_for_cmd(&queue, size));
+ EXPECT_EQ(UINT64_C(0x123456789abcdef0), storage.canary);
+ }
+ }
+ cxi_cq_init(&queue, storage.commands, count, NULL, 0);
+ EXPECT_EQ(-EINVAL, cxi_cq_align_for_cmd(&queue, (enum c_cmd_size)-1));
+ EXPECT_EQ(-EINVAL, cxi_cq_align_for_cmd(&queue, ARRAY_SIZE(__cxi_cmd_slots)));
+ queue.size32--;
+ EXPECT_EQ(-EINVAL, cxi_cq_align_for_cmd(&queue, C_CMD_SIZE_64B));
+ correctness_result("command-ring-bounds", before);
+}
+
+static void test_queue_full_errno(void)
+{
+ struct cxi_context context = {};
+ struct cxi_qp qp = {};
+ struct cxi_ibv_cq cq = {};
+ struct cxi_cmd64 commands[8] = {};
+ struct cxi_mr mr = { .va = 0x1000, .iova = 0x1000, .len = 64 };
+ struct ibv_sge sge = { .addr = 0x1000, .length = 64, .lkey = 1 };
+ struct cxi_ah address = { .ah_ex.remote_qpn = 1, .nid = 0x807 };
+ struct ibv_send_wr send = { .opcode = IBV_WR_SEND, .sg_list = &sge, .num_sge = 1 };
+ struct ibv_recv_wr receive = { .sg_list = &sge, .num_sge = 1 };
+ struct ibv_send_wr *bad_send = NULL;
+ struct ibv_recv_wr *bad_receive = NULL;
+ int before = failed_tests;
+ int ret;
+
+ context.verbs_ctx.sz = sizeof(context.verbs_ctx);
+ context.verbs_ctx.context.abi_compat = __VERBS_ABI_IS_EXTENDED;
+ context.pid_bits = 9;
+ mr.vmr.ibv_mr.lkey = 1;
+ context.mr_list = &mr;
+ ret = pthread_spin_init(&context.mr_list_lock, PTHREAD_PROCESS_PRIVATE);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ return;
+ ret = cxi_qp_init_locks(&qp);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_spin_destroy(&context.mr_list_lock);
+ return;
+ }
+ qp.vqp.qp.context = &context.verbs_ctx.context;
+ qp.vqp.qp.send_cq = &cq.verbs_cq.cq;
+ qp.sq_size = 1;
+ qp.rq_size = 1;
+ qp.job_id = 1;
+ cxi_cq_init(&qp.hw_txq, commands, ARRAY_SIZE(commands), NULL, 0);
+ qp.hw_txq.wp32 = qp.hw_txq.size32 - 2;
+ qp.hw_tgq = qp.hw_txq;
+ send.wr.ru.ah = &address.ah_ex;
+ EXPECT_EQ(ENOSPC, cxi_post_send(&qp.vqp.qp, &send, &bad_send));
+ EXPECT_EQ((uintptr_t)&send, (uintptr_t)bad_send);
+ EXPECT_EQ(ENOSPC, cxi_post_recv(&qp.vqp.qp, &receive, &bad_receive));
+ EXPECT_EQ((uintptr_t)&receive, (uintptr_t)bad_receive);
+ pthread_mutex_destroy(&qp.builder_lock);
+ pthread_spin_destroy(&qp.sq_lock);
+ pthread_spin_destroy(&qp.rq_lock);
+ pthread_spin_destroy(&context.mr_list_lock);
+ correctness_result("queue-full-positive-errno", before);
+}
+
+static void test_telemetry_api_bounds(void)
+{
+ const uint64_t *items = NULL;
+ uint32_t count = 0;
+ int before = failed_tests;
+
+ EXPECT_EQ(EINVAL, cxidv_telem_get_set((enum cxidv_telem_set)-1, &items, &count));
+ EXPECT_EQ(EINVAL, cxidv_telem_get_set(CXIDV_TELEM_SET_COUNT, &items, &count));
+ EXPECT_EQ(0, cxidv_telem_lookup("UNDEFINED"));
+ EXPECT_EQ(-1, cxidv_telem_lookup(NULL));
+ EXPECT_EQ(CXI_IBV_TC_ENTRY_F_CREATED_BY_RDMA, CXIDV_TC_ENTRY_F_CREATED_BY_RDMA);
+ correctness_result("telemetry-api-bounds", before);
+}
+
+struct tc_cache_stress {
+ struct cxi_context context;
+ unsigned int failures;
+};
+
+static void *update_tc_cache(void *opaque)
+{
+ struct tc_cache_stress *test = opaque;
+
+ for (unsigned int iteration = 0; iteration < 100000; iteration++) {
+ uint32_t tc = iteration % CXI_IBV_TC_MAX;
+
+ cxi_tc_cache_update(&test->context, tc, tc + 1000, true);
+ }
+
+ return NULL;
+}
+
+static void *read_tc_cache(void *opaque)
+{
+ struct tc_cache_stress *test = opaque;
+
+ for (unsigned int iteration = 0; iteration < 100000; iteration++) {
+ uint32_t tc, svc_id;
+
+ if (!cxi_tc_cache_read(&test->context, &tc, &svc_id) ||
+ tc >= CXI_IBV_TC_MAX || svc_id != tc + 1000)
+ test->failures++;
+ }
+
+ return NULL;
+}
+
+static void test_tc_cache_concurrency(void)
+{
+ struct tc_cache_stress test = {};
+ pthread_t updater, reader;
+ int before = failed_tests;
+ int ret;
+
+ ret = pthread_mutex_init(&test.context.tc_lock, NULL);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ return;
+ cxi_tc_cache_update(&test.context, 0, 1000, true);
+ ret = pthread_create(&updater, NULL, update_tc_cache, &test);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_mutex_destroy(&test.context.tc_lock);
+ return;
+ }
+ ret = pthread_create(&reader, NULL, read_tc_cache, &test);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_join(updater, NULL);
+ pthread_mutex_destroy(&test.context.tc_lock);
+ return;
+ }
+ pthread_join(updater, NULL);
+ pthread_join(reader, NULL);
+ EXPECT_EQ(0, test.failures);
+ pthread_mutex_destroy(&test.context.tc_lock);
+ correctness_result("tc-cache-concurrency", before);
+}
+
+struct wait_hold_test {
+ struct cxidv_wait *wait;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ bool held;
+ bool release;
+ int get_ret;
+};
+
+static void *hold_wait_reference(void *opaque)
+{
+ struct wait_hold_test *test = opaque;
+
+ pthread_mutex_lock(&test->lock);
+ test->get_ret = cxi_wait_ref_get(test->wait);
+ test->held = true;
+ pthread_cond_signal(&test->cond);
+ while (!test->release)
+ pthread_cond_wait(&test->cond, &test->lock);
+ if (!test->get_ret)
+ cxi_wait_ref_put(test->wait);
+ pthread_mutex_unlock(&test->lock);
+
+ return NULL;
+}
+
+static void test_wait_lifetime_contract(void)
+{
+ struct cxidv_lni lni = {};
+ struct cxidv_wait *wait = NULL;
+ struct wait_hold_test hold = {};
+ pthread_t thread;
+ int pipe_fds[2] = { -1, -1 };
+ bool read_fd_open = false;
+ int before = failed_tests;
+ int ret;
+
+ EXPECT_EQ(EINVAL, cxidv_destroy_wait(NULL));
+ ret = pipe(pipe_fds);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ goto out;
+ read_fd_open = true;
+ wait = calloc(1, sizeof(*wait));
+ EXPECT_TRUE(wait);
+ if (!wait)
+ goto out;
+ wait->lni = &lni;
+ wait->fd = pipe_fds[0];
+ ret = pthread_mutex_init(&hold.lock, NULL);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ goto out;
+ ret = pthread_cond_init(&hold.cond, NULL);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_mutex_destroy(&hold.lock);
+ goto out;
+ }
+ hold.wait = wait;
+ ret = pthread_create(&thread, NULL, hold_wait_reference, &hold);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_cond_destroy(&hold.cond);
+ pthread_mutex_destroy(&hold.lock);
+ goto out;
+ }
+ pthread_mutex_lock(&hold.lock);
+ while (!hold.held)
+ pthread_cond_wait(&hold.cond, &hold.lock);
+ EXPECT_EQ(0, hold.get_ret);
+ if (!hold.get_ret)
+ EXPECT_EQ(EBUSY, cxidv_destroy_wait(wait));
+ hold.release = true;
+ pthread_cond_signal(&hold.cond);
+ pthread_mutex_unlock(&hold.lock);
+ pthread_join(thread, NULL);
+ pthread_cond_destroy(&hold.cond);
+ pthread_mutex_destroy(&hold.lock);
+ if (!hold.get_ret) {
+ EXPECT_EQ(0, cxi_wait_ref_get(wait));
+ EXPECT_EQ(0, cxi_wait_ref_get(wait));
+ EXPECT_EQ(EBUSY, cxidv_destroy_wait(wait));
+ cxi_wait_ref_put(wait);
+ cxi_wait_ref_put(wait);
+ ret = cxidv_destroy_wait(wait);
+ EXPECT_EQ(0, ret);
+ if (!ret) {
+ read_fd_open = false;
+ wait = NULL;
+ }
+ }
+out:
+ if (read_fd_open)
+ close(pipe_fds[0]);
+ if (wait)
+ free(wait);
+ if (pipe_fds[1] >= 0)
+ close(pipe_fds[1]);
+ correctness_result("wait-lifetime-contract", before);
+}
+
+static void test_failed_qp_destroy(void)
+{
+ struct cxi_context context = {};
+ struct cxi_qp qp = {};
+ int before = failed_tests;
+ int ret;
+
+ ret = pthread_spin_init(&context.qp_table_lock, PTHREAD_PROCESS_PRIVATE);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ return;
+ qp.pte_index = 12;
+ qp.qp_slot = UINT32_MAX;
+ pthread_spin_lock(&context.qp_table_lock);
+ ret = cxi_qp_register_locked(&context, &qp);
+ pthread_spin_unlock(&context.qp_table_lock);
+ EXPECT_EQ(0, ret);
+ if (ret) {
+ pthread_spin_destroy(&context.qp_table_lock);
+ correctness_result("failed-qp-destroy-retains-lookup", before);
+ return;
+ }
+ qp.vqp.qp.context = &context.verbs_ctx.context;
+ fail_destroy_qp_command = true;
+ EXPECT_EQ(EBUSY, cxi_destroy_qp(&qp.vqp.qp));
+ pthread_spin_lock(&context.qp_table_lock);
+ EXPECT_EQ((uintptr_t)&qp, (uintptr_t)context.qp_slots[qp.qp_slot]);
+ EXPECT_EQ((uintptr_t)&qp,
+ (uintptr_t)cxi_qp_find_pte_locked(&context, qp.pte_index));
+ pthread_spin_unlock(&context.qp_table_lock);
+ fail_destroy_qp_command = false;
+ pthread_spin_lock(&context.qp_table_lock);
+ cxi_qp_unregister_locked(&context, &qp);
+ pthread_spin_unlock(&context.qp_table_lock);
+ pthread_spin_destroy(&context.qp_table_lock);
+ correctness_result("failed-qp-destroy-retains-lookup", before);
+}
+
+static void test_sge_bounds(void)
+{
+ struct cxi_context context = {};
+ struct cxi_mr mr = {
+ .vmr.ibv_mr.lkey = 7,
+ .lkey64 = 3,
+ .va = 0x1000,
+ .iova = 0x8000,
+ .len = 0x100,
+ };
+ struct ibv_sge sge = { .addr = 0x1080, .length = 0x80, .lkey = 7 };
+ uint64_t iova = 0;
+ uint8_t lac = 0;
+ int before = failed_tests;
+ int ret;
+
+ ret = pthread_spin_init(&context.mr_list_lock, PTHREAD_PROCESS_PRIVATE);
+ EXPECT_EQ(0, ret);
+ if (ret)
+ return;
+ context.mr_list = &mr;
+ EXPECT_TRUE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ EXPECT_EQ(0x8080, iova);
+ EXPECT_EQ(3, lac);
+ sge.length++;
+ EXPECT_FALSE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ sge.addr = UINT64_MAX - 15;
+ sge.length = 32;
+ EXPECT_FALSE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ mr.va = UINT64_MAX - 31;
+ mr.len = 64;
+ EXPECT_FALSE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ mr.va = 0x1000;
+ mr.len = 0x100;
+ mr.iova = UINT64_MAX - 7;
+ sge.addr = 0x1010;
+ sge.length = 1;
+ EXPECT_FALSE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ mr.iova = 0x8000;
+ sge.addr = 0x1100;
+ sge.length = 0;
+ EXPECT_TRUE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ sge.lkey++;
+ EXPECT_FALSE(cxi_sge_resolve(&context, &sge, &iova, &lac));
+ pthread_spin_destroy(&context.mr_list_lock);
+ correctness_result("sge-bounds", before);
+}
+
+int main(int argc, char **argv)
+{
+ test_command_ring_bounds();
+ test_queue_full_errno();
+ test_telemetry_api_bounds();
+ test_tc_cache_concurrency();
+ test_wait_lifetime_contract();
+ test_failed_qp_destroy();
+ test_qp_lookup_indexes();
+ test_sge_bounds();
+ test_indexed_submission_locking();
+ test_out_of_order_send_completions();
+ test_model_enforces_match_id();
+ test_recv_fields();
+ test_recv_selection();
+ test_region_fields();
+ test_region_selection();
+ test_region_unlink();
+ test_create_qp_resp_abi();
+ test_vni_key_encoding();
+ test_csr_accessors();
+ if (argc == 3 && !strcmp(argv[1], "--resources")) {
+ test_hardware_resources(argv[2]);
+ } else if (argc == 3 && !strcmp(argv[1], "--csr")) {
+ test_csr_mapping(argv[2]);
+ } else if (argc != 1) {
+ fprintf(stderr, "usage: %s [--resources DEVICE | --csr DEVICE]\n", argv[0]);
+ return 2;
+ }
+
+ if (failed_tests) {
+ printf("cxi_le_test: %d check(s) FAILED\n", failed_tests);
+ return 1;
+ }
+ printf("cxi_le_test: all checks passed\n");
+ return 0;
+}
diff --git a/tests/base.py b/tests/base.py
index 60b7d09c38f3..b01f8f97384b 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -12,10 +12,15 @@ import stat
import json
import sys
import os
+from contextlib import contextmanager

from pyverbs.qp import QPCap, QPInitAttrEx, QPInitAttr, QPAttr, QP
from pyverbs.srq import SRQ, SrqInitAttrEx, SrqInitAttr, SrqAttr
from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsError
+try:
+ from pyverbs.providers.cxi.cxidv import LNI
+except ImportError:
+ LNI = None
from pyverbs.addr import AHAttr, GlobalRoute
from pyverbs.xrcd import XRCD, XRCDInitAttr
from pyverbs.device import Context
@@ -45,6 +50,7 @@ MLNX_VENDOR_ID = 0x02c9
CX3_MLNX_PART_ID = 4099
CX3Pro_MLNX_PART_ID = 4103
DCT_KEY = 0xbadc0de
+IBV_DEVICE_RU = 1 << 42
# Dictionary: vendor_id -> array of part_ids of devices that lack RoCEv2 support
ROCEV2_UNSUPPORTED_DEVS = {MLNX_VENDOR_ID: [CX3Pro_MLNX_PART_ID,
CX3_MLNX_PART_ID]}
@@ -71,6 +77,35 @@ def is_gid_available(gid_index):
raise unittest.SkipTest(f'No relevant GID found')


+def create_ru_lni(ctx):
+ """Create the context LNI required by CXI PD allocation, when supported."""
+ if LNI is None:
+ return None
+ if not ctx.query_device_ex().device_cap_flags_ex & IBV_DEVICE_RU:
+ return None
+ try:
+ return LNI(ctx)
+ except PyverbsRDMAError as ex:
+ if ex.error_code == errno.EOPNOTSUPP:
+ return None
+ raise
+
+
+def is_uet_ru_device(ctx):
+ return bool(ctx.query_device_ex().device_cap_flags_ex & IBV_DEVICE_RU)
+
+
+@contextmanager
+def context_with_ru_lni(dev_name):
+ """Open a test context with the LNI required by CXI PD allocation."""
+ ctx = d.Context(name=dev_name)
+ lni = create_ru_lni(ctx)
+ try:
+ yield ctx
+ finally:
+ ctx.close()
+
+
class PyverbsAPITestCase(unittest.TestCase):
def __init__(self, methodName='runTest'):
super().__init__(methodName)
@@ -78,6 +113,7 @@ class PyverbsAPITestCase(unittest.TestCase):
self.config = parser.get_config()
self.dev_name = None
self.ctx = None
+ self.lni = None
self.attr = None
self.attr_ex = None
self.gid_index = 0
@@ -108,6 +144,7 @@ class PyverbsAPITestCase(unittest.TestCase):

def create_context(self):
self.ctx = d.Context(name=self.dev_name)
+ self.lni = create_ru_lni(self.ctx)

def set_env_variable(self, var, value):
"""
@@ -342,6 +379,10 @@ class RDMATestCase(unittest.TestCase):
:param resource_arg: Dict of args that specify the resource specific
attributes.
"""
+ with d.Context(name=self.dev_name) as ctx:
+ if is_uet_ru_device(ctx) and not getattr(resource, 'supports_ru', False):
+ raise unittest.SkipTest(
+ f'{resource.__name__} requires a connected QP, not an RU QP')
try:
self.client = resource(**self.dev_info, **resource_arg)
self.server = resource(**self.dev_info, **resource_arg)
@@ -382,6 +423,9 @@ class RDMACMBaseTest(RDMATestCase):
"""
def setUp(self):
super().setUp()
+ with d.Context(name=self.dev_name) as ctx:
+ if is_uet_ru_device(ctx):
+ raise unittest.SkipTest('RDMACM connection tests do not apply to RU QPs')
if not self.ip_addr:
raise unittest.SkipTest('Device {} doesn\'t have net interface'
.format(self.dev_name))
@@ -576,11 +620,14 @@ class BaseResources(object, metaclass=SkipTestMeta):
self.gid_index = gid_index
self.ib_port = ib_port
self.ctx = None
+ self.lni = None
+ self.lni = None
self.create_context()
self.create_pd()

def create_context(self):
self.ctx = Context(name=self.dev_name)
+ self.lni = create_ru_lni(self.ctx)

def create_pd(self):
self.pd = PD(self.ctx)
diff --git a/tests/ru_base.py b/tests/ru_base.py
index 74cd7fca86d3..57763a125968 100644
--- a/tests/ru_base.py
+++ b/tests/ru_base.py
@@ -29,6 +29,10 @@ from pyverbs.pyverbs_error import PyverbsRDMAError
from pyverbs.qp import QPCap, QPInitAttrEx, QPEx
from pyverbs.addr import GID, GlobalRoute, AHAttr
from pyverbs.ru import Job, JobAttr, JKey, AHAttrEx
+try:
+ from pyverbs.providers.cxi.cxidv import LNI, alloc_pid
+except ImportError:
+ LNI = alloc_pid = None
import pyverbs.device as d
from pyverbs.libibverbs_enums import ibv_qp_init_attr_mask, ibv_qp_type, \
ibv_qp_create_send_ops_flags, ibv_access_flags, ibv_job_attr_mask, \
@@ -104,6 +108,8 @@ class RuAPITestCase(PyverbsAPITestCase):
def setUp(self):
super().setUp()
skip_if_not_ru_dev(self.ctx)
+ if self.lni is None and LNI is not None:
+ self.lni = LNI(self.ctx)


class RuRDMATestCase(RDMATestCase):
@@ -137,11 +143,10 @@ class RuResources(TrafficResources):
A UET player: a job, a job key, and one or more RU queue pairs that reach
the other player through the job's shared address table.
"""
- # The JobID is 24 bits on the wire (uet_pkt_hdr.h,
- # UET_SES_REQ_JOB_ID_MASK). A wider id is accepted at allocation and
- # then truncated in the header, so the far end answers Bad Job ID.
- JOB_ID = 0x756574
+ # The kernel ABI currently limits JobIDs to 16 bits.
+ JOB_ID = 0x7564
INITIATOR_ID = 16
+ supports_ru = True
MAX_ADDR_ENTRIES = 16
# Resource indices this test suite owns. A queue pair that shares an
# index with another receives the other's stragglers, so the two players
@@ -170,14 +175,22 @@ class RuResources(TrafficResources):
ibv_access_flags.IBV_ACCESS_REMOTE_READ)
self.job = None
self.jkey = None
+ self._pid_on_fep = None
self.player = 0
self.remote_addr_idx = None
super().__init__(dev_name, ib_port, gid_index, qp_count=qp_count,
msg_size=msg_size)

- def init_resources(self):
- # the job has to exist before the queue pairs that join it
+ def create_context(self):
+ super().create_context()
+
+ def create_pd(self):
self.create_job()
+ super().create_pd()
+ self._pid_on_fep = alloc_pid(self.pd)
+ self.jkey = JKey(self.pd, self.job)
+
+ def init_resources(self):
super().init_resources()

def create_job(self):
@@ -194,7 +207,6 @@ class RuResources(TrafficResources):
if ex.error_code == errno.EOPNOTSUPP:
raise unittest.SkipTest('UET jobs are not supported')
raise ex
- self.jkey = JKey(self.pd, self.job)

def res_index(self, qp_idx):
"""
@@ -210,7 +222,7 @@ class RuResources(TrafficResources):
device-wide by the first protection domain that uses it, so the two
players - which have a protection domain each - cannot share one.
"""
- return self.player
+ return self._pid_on_fep

def addr_index(self, qp_idx):
"""
@@ -269,8 +281,9 @@ class RuResources(TrafficResources):
self.rpsns = rpsns
self.rqps_num = rqps_num
self.remote_addr_idx = []
- gr = GlobalRoute(dgid=ipv4_to_gid(self.local_ip),
- flow_label=self.INITIATOR_ID, sgid_index=0,
+ gr = GlobalRoute(dgid=self.ctx.query_gid(self.ib_port, self.gid_index),
+ flow_label=self.INITIATOR_ID,
+ sgid_index=self.gid_index,
hop_limit=1)
for qp_idx in range(self.qp_count):
ah_attr = AHAttr(is_global=1, port_num=self.ib_port, gr=gr)
diff --git a/tests/test_addr.py b/tests/test_addr.py
index 0648dc680dbe..7ab0922fc9fa 100644
--- a/tests/test_addr.py
+++ b/tests/test_addr.py
@@ -5,7 +5,7 @@ import unittest
import errno

from pyverbs.pyverbs_error import PyverbsError, PyverbsRDMAError
-from tests.base import PyverbsAPITestCase
+from tests.base import PyverbsAPITestCase, is_uet_ru_device
from pyverbs.addr import AHAttr, AH
import pyverbs.device as d
from pyverbs.libibverbs_enums import ibv_port_state, IBV_LINK_LAYER_ETHERNET, IBV_LINK_LAYER_INFINIBAND
@@ -54,6 +54,8 @@ class AHTest(PyverbsAPITestCase):
"""
Verify that AH can't be created without GRH in RoCE
"""
+ if is_uet_ru_device(self.ctx):
+ raise unittest.SkipTest('RoCE address handles do not apply to UET RU')
self.verify_link_layer_ether(self.ctx)
self.verify_state(self.ctx)
pd = PD(self.ctx)
diff --git a/tests/test_device.py b/tests/test_device.py
index a56fa8614059..4a68a478fef6 100644
--- a/tests/test_device.py
+++ b/tests/test_device.py
@@ -12,7 +12,7 @@ import errno
import os

from pyverbs.pyverbs_error import PyverbsError, PyverbsRDMAError
-from tests.base import PyverbsAPITestCase
+from tests.base import PyverbsAPITestCase, is_uet_ru_device
from pyverbs.device import Context, DM
import tests.utils as u
import pyverbs.device as d
@@ -60,7 +60,7 @@ class DeviceTest(PyverbsAPITestCase):
for dev in self.get_device_list():
with d.Context(name=dev.name.decode()) as ctx:
attr = ctx.query_device()
- self.verify_device_attr(attr, dev)
+ self.verify_device_attr(attr, dev, is_uet_ru_device(ctx))

def test_query_pkey(self):
"""
@@ -187,7 +187,7 @@ class DeviceTest(PyverbsAPITestCase):
f'non-existent gid index {no_gid_index}')

@staticmethod
- def verify_device_attr(attr, device):
+ def verify_device_attr(attr, device, is_ru=False):
"""
Helper method that verifies correctness of some members of DeviceAttr
object.
@@ -197,7 +197,8 @@ class DeviceTest(PyverbsAPITestCase):
"""
if device.node_type != ibv_node_type.IBV_NODE_UNSPECIFIED and device.node_type != ibv_node_type.IBV_NODE_UNKNOWN:
assert attr.node_guid != 0
- assert attr.sys_image_guid != 0
+ if not is_ru:
+ assert attr.sys_image_guid != 0
assert attr.max_mr_size > PAGE_SIZE
assert attr.page_size_cap >= PAGE_SIZE
assert attr.vendor_id != 0
@@ -219,7 +220,8 @@ class DeviceTest(PyverbsAPITestCase):
for dev in self.get_device_list():
with d.Context(name=dev.name.decode()) as ctx:
attr_ex = ctx.query_device_ex()
- self.verify_device_attr(attr_ex.orig_attr, dev)
+ self.verify_device_attr(attr_ex.orig_attr, dev,
+ is_uet_ru_device(ctx))

def test_phys_port_cnt_ex(self):
"""
diff --git a/tests/test_dmah.py b/tests/test_dmah.py
index 885855f92670..7f07dad0868d 100644
--- a/tests/test_dmah.py
+++ b/tests/test_dmah.py
@@ -5,7 +5,6 @@ from pyverbs.libibverbs_enums import ibv_access_flags, ibv_wr_opcode, ibv_odp_tr
ibv_tph_mem_type
from tests.base import PyverbsAPITestCase, RCResources, RDMATestCase
from pyverbs.pyverbs_error import PyverbsError, PyverbsRDMAError
-import pyverbs.device as d
from pyverbs.pd import PD
from pyverbs.mr import MREx, DMAHandle
from pyverbs.qp import QPAttr
@@ -16,65 +15,58 @@ class DMAHandleTest(PyverbsAPITestCase):
@u.skip_unsupported
def test_dmah_with_mrex(self):
"""Verify DMAHandle can be used during MREx registration."""
- with d.Context(name=self.dev_name) as ctx:
- with PD(ctx) as pd:
- attr = u.create_dmah_init_attr()
- with DMAHandle(ctx, attr) as dmah:
- length = u.get_mr_length()
- access = ibv_access_flags.IBV_ACCESS_LOCAL_WRITE
- with MREx(pd, length, access, dmah=dmah) as mr:
- pass
+ with PD(self.ctx) as pd:
+ attr = u.create_dmah_init_attr()
+ with DMAHandle(self.ctx, attr) as dmah:
+ length = u.get_mr_length()
+ access = ibv_access_flags.IBV_ACCESS_LOCAL_WRITE
+ with MREx(pd, length, access, dmah=dmah) as mr:
+ pass

@u.skip_unsupported
def test_dmah_invalid_ph(self):
"""Verify DMAHandle with invalid PH value, max ph value is 3."""
- with d.Context(name=self.dev_name) as ctx:
- with PD(ctx) as pd:
- attr = u.create_dmah_init_attr(ph=4)
- with self.assertRaises(PyverbsError):
- DMAHandle(ctx, attr)
+ with PD(self.ctx) as pd:
+ attr = u.create_dmah_init_attr(ph=4)
+ with self.assertRaises(PyverbsError):
+ DMAHandle(self.ctx, attr)

@u.skip_unsupported
def test_dmah_persistent_memory(self):
"""Attempt to create DMAHandle targeting persistent memory."""
- with d.Context(name=self.dev_name) as ctx:
- attr = u.create_dmah_init_attr(tph_mem_type=ibv_tph_mem_type.IBV_TPH_MEM_TYPE_PM)
- with DMAHandle(ctx, attr):
- pass
+ attr = u.create_dmah_init_attr(tph_mem_type=ibv_tph_mem_type.IBV_TPH_MEM_TYPE_PM)
+ with DMAHandle(self.ctx, attr):
+ pass

@u.skip_unsupported
def test_dmah_invalid_mem_type(self):
"""Pass an unsupported TPH memory-type and verify provider rejects it."""
- with d.Context(name=self.dev_name) as ctx:
- attr = u.create_dmah_init_attr(tph_mem_type=0xFE)
- with self.assertRaises(PyverbsError):
- DMAHandle(ctx, attr)
+ attr = u.create_dmah_init_attr(tph_mem_type=0xFE)
+ with self.assertRaises(PyverbsError):
+ DMAHandle(self.ctx, attr)

@u.skip_unsupported
def test_dmah_inval_cpu_id(self):
"""Attempt to create DMAHandle with invalid CPU ID (0xffff). Expect failure."""
- with d.Context(name=self.dev_name) as ctx:
- attr = u.create_dmah_init_attr(cpu_id=0xffff, ph=3)
- with self.assertRaises(PyverbsError):
- DMAHandle(ctx, attr)
+ attr = u.create_dmah_init_attr(cpu_id=0xffff, ph=3)
+ with self.assertRaises(PyverbsError):
+ DMAHandle(self.ctx, attr)

@u.skip_unsupported
def test_dmah_mrex_odp_bad_flow(self):
"""Attempt to register ODP-capable MREx with DMAHandle.
Expect failure since ODP isn't supported with DMAHandle."""
- with d.Context(name=self.dev_name) as ctx:
- # Check ODP support; skip if not available
- odp_cap = (ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_SEND |
- ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_RECV)
- u.odp_supported(ctx, 'rc', odp_cap)
- with PD(ctx) as pd:
- attr = u.create_dmah_init_attr()
- dmah = DMAHandle(ctx, attr)
- with self.assertRaises(PyverbsError):
- length = u.get_mr_length()
- access = (ibv_access_flags.IBV_ACCESS_LOCAL_WRITE |
- ibv_access_flags.IBV_ACCESS_ON_DEMAND)
- MREx(pd, length, access, dmah=dmah)
+ odp_cap = (ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_SEND |
+ ibv_odp_transport_cap_bits.IBV_ODP_SUPPORT_RECV)
+ u.odp_supported(self.ctx, 'rc', odp_cap)
+ with PD(self.ctx) as pd:
+ attr = u.create_dmah_init_attr()
+ dmah = DMAHandle(self.ctx, attr)
+ with self.assertRaises(PyverbsError):
+ length = u.get_mr_length()
+ access = (ibv_access_flags.IBV_ACCESS_LOCAL_WRITE |
+ ibv_access_flags.IBV_ACCESS_ON_DEMAND)
+ MREx(pd, length, access, dmah=dmah)


class DmaHandleMRExRC(RCResources):
diff --git a/tests/test_mr.py b/tests/test_mr.py
index b7ef04e8c32b..d98b93c276de 100644
--- a/tests/test_mr.py
+++ b/tests/test_mr.py
@@ -8,7 +8,7 @@ import unittest
import random
import errno

-from tests.base import PyverbsAPITestCase, RCResources, RDMATestCase
+from tests.base import PyverbsAPITestCase, RCResources, RDMATestCase, context_with_ru_lni
from pyverbs.pyverbs_error import PyverbsRDMAError, PyverbsError
from pyverbs.mr import MR, MW, DMMR, DmaBufMR, MWBindInfo, MWBind, MREx
from pyverbs.mem_alloc import posix_memalign, free
@@ -202,7 +202,7 @@ class MRTest(RDMATestCase):
"""Validate that registering a given MR class with illegal flags fails."""
bad_flags = [ibv_access_flags.IBV_ACCESS_REMOTE_WRITE,
ibv_access_flags.IBV_ACCESS_REMOTE_ATOMIC]
- with d.Context(name=self.dev_name) as ctx:
+ with context_with_ru_lni(self.dev_name) as ctx:
with PD(ctx) as pd:
for flag in bad_flags:
with self.assertRaisesRegex(PyverbsRDMAError, expected_regex):
@@ -222,7 +222,7 @@ class MRTest(RDMATestCase):

def test_reg_mrex_inval_params(self):
"""Verify MREx registration fails when ADDR & FD flags set."""
- with d.Context(name=self.dev_name) as ctx:
+ with context_with_ru_lni(self.dev_name) as ctx:
with PD(ctx) as pd:
with self.assertRaises(PyverbsRDMAError):
MREx(pd, u.get_mr_length(), ibv_access_flags.IBV_ACCESS_LOCAL_WRITE,
@@ -421,7 +421,7 @@ class MWTest(RDMATestCase):
"""
Verify that trying to create a MW of a wrong type fails
"""
- with d.Context(name=self.dev_name) as ctx:
+ with context_with_ru_lni(self.dev_name) as ctx:
with PD(ctx) as pd:
try:
mw_type = 3
diff --git a/tests/test_qp.py b/tests/test_qp.py
index 81debb17adbf..e2facb53b30e 100644
--- a/tests/test_qp.py
+++ b/tests/test_qp.py
@@ -13,7 +13,7 @@ import os

from pyverbs.pyverbs_error import PyverbsRDMAError
from pyverbs.qp import QPAttr, QP, QPRateLimitAttr
-from tests.base import PyverbsAPITestCase, RDMATestCase, RCResources
+from tests.base import PyverbsAPITestCase, RDMATestCase, RCResources, is_uet_ru_device
import pyverbs.utils as pu
import pyverbs.device as d
from pyverbs.libibverbs_enums import ibv_qp_type, ibv_qp_state, ibv_qp_attr_mask, ibv_node_type, \
@@ -28,6 +28,11 @@ class QPTest(PyverbsAPITestCase):
Test various functionalities of the QP class.
"""

+ def setUp(self):
+ super().setUp()
+ if is_uet_ru_device(self.ctx):
+ raise unittest.SkipTest('Connected RC/UC/UD/RAW QP tests do not apply to RU')
+
def create_qp(self, creator, qp_init_attr, is_ex, with_attr, port_num):
"""
Auxiliary function to create QP object.
diff --git a/tests/test_ru.py b/tests/test_ru.py
index f2d7a6e115a1..ec4598af5991 100644
--- a/tests/test_ru.py
+++ b/tests/test_ru.py
@@ -29,7 +29,7 @@ from pyverbs.libibverbs_enums import ibv_qp_init_attr_mask, ibv_qp_type, \
ibv_gid_type_sysfs, IBV_LINK_LAYER_ETHERNET

from tests.ru_base import RuAPITestCase, RuRDMATestCase, \
- RuResources, ipv4_to_gid
+ RuResources, alloc_pid, ipv4_to_gid
import tests.utils as u


@@ -37,7 +37,7 @@ class RuJobTest(RuAPITestCase):
"""
Jobs and their address tables.
"""
- JOB_ID = 0x756575
+ JOB_ID = 0x7565
MAX_ADDR_ENTRIES = 16

def job_attr(self, job_id=None, max_addr_entries=None):
@@ -74,13 +74,12 @@ class RuJobTest(RuAPITestCase):

def test_duplicate_job_id(self):
"""
- One job id, one job. A device that let a second job take a live id
- would have no way to decide which one an incoming packet belongs to.
+ A context has one job binding; a second live job is busy.
"""
with Job(self.ctx, self.job_attr()):
with self.assertRaises(PyverbsRDMAError) as ctx:
Job(self.ctx, self.job_attr())
- self.assertEqual(ctx.exception.error_code, errno.EEXIST)
+ self.assertEqual(ctx.exception.error_code, errno.EBUSY)

def test_export_import_job(self):
"""
@@ -118,12 +117,12 @@ class RuJobTest(RuAPITestCase):

def test_job_needs_addr_table(self):
"""
- The address table is sized once, when the job is allocated, so a job
- that does not say how large is refused.
+ A zero-sized address table is valid for jobs that only use fixed peers.
"""
- with self.assertRaises(PyverbsRDMAError) as ctx:
- Job(self.ctx, self.job_attr(max_addr_entries=0))
- self.assertEqual(ctx.exception.error_code, errno.EINVAL)
+ with Job(self.ctx, self.job_attr(max_addr_entries=0)) as job:
+ self.assertEqual(job.query().max_addr_entries, 0)
+ with self.assertRaises(PyverbsRDMAError):
+ job.insert_addr(self.peer(), 0)

def test_import_bad_fd(self):
"""
@@ -169,11 +168,14 @@ class RuJobTest(RuAPITestCase):

def test_overwrite_addr(self):
"""
- Re-inserting an entry replaces it. A peer that moves has to be
- reachable without tearing down the job.
+ An occupied address-table entry must be removed before replacement.
"""
with Job(self.ctx, self.job_attr()) as job:
job.insert_addr(self.peer(remote_qpn=0x19000), 2)
+ with self.assertRaises(PyverbsRDMAError) as ctx:
+ job.insert_addr(self.peer(remote_qpn=0x1a000), 2)
+ self.assertEqual(ctx.exception.error_code, errno.EBUSY)
+ job.remove_addr(2)
job.insert_addr(self.peer(remote_qpn=0x1a000), 2)
self.assertEqual(job.query_addr(2).remote_qpn, 0x1a000)

@@ -182,7 +184,7 @@ class RuJKeyTest(RuAPITestCase):
"""
Job keys, the binding between a protection domain and a job.
"""
- JOB_ID = 0x756576
+ JOB_ID = 0x7566

def job_attr(self):
return JobAttr(comp_mask=ibv_job_attr_mask.IBV_JOB_ATTR_ID |
@@ -195,31 +197,29 @@ class RuJKeyTest(RuAPITestCase):
def test_create_destroy_jkey(self):
with PD(self.ctx) as pd, Job(self.ctx, self.job_attr()) as job:
with JKey(pd, job) as jkey:
- self.assertNotEqual(jkey.jkey, 0)
+ pass

- def test_jkeys_are_distinct(self):
+ def test_jkeys_can_share_a_job_cp(self):
"""
- Two protection domains joining one job get different keys, which is
- what lets the device tell whose request is whose.
+ CXI JobKeys carry a CP LCID, which can be shared by PDs using the same
+ job and traffic class; the PIDonFEP still distinguishes the PDs.
"""
with Job(self.ctx, self.job_attr()) as job:
with PD(self.ctx) as pd1, PD(self.ctx) as pd2:
with JKey(pd1, job) as k1, JKey(pd2, job) as k2:
- self.assertNotEqual(k1.jkey, k2.jkey)
+ self.assertEqual(k1.jkey, k2.jkey)


class RuQPTest(RuAPITestCase):
"""
RU queue pairs: creation, the states they accept, and memory regions.
"""
- JOB_ID = 0x756577
+ JOB_ID = 0x7567
RES_INDEX = 40
INITIATOR_ID = 16

def setUp(self):
super().setUp()
- self.pd = PD(self.ctx)
- self.cq = CQ(self.ctx, 16, None, None, 0)
self.job = Job(self.ctx,
JobAttr(comp_mask=ibv_job_attr_mask.IBV_JOB_ATTR_ID |
ibv_job_attr_mask.IBV_JOB_ATTR_MAX_ADDR_ENTRIES |
@@ -227,6 +227,9 @@ class RuQPTest(RuAPITestCase):
ibv_job_attr_mask.IBV_JOB_ATTR_SGID_INDEX,
id=self.JOB_ID, max_addr_entries=16,
port_num=self.ib_port, sgid_index=0))
+ self.pd = PD(self.ctx)
+ self.pid_on_fep = alloc_pid(self.pd)
+ self.cq = CQ(self.ctx, 16, None, None, 0)
self.jkey = JKey(self.pd, self.job)

def tearDown(self):
@@ -251,7 +254,7 @@ class RuQPTest(RuAPITestCase):
comp_mask=comp_mask, jkey=self.jkey if jkey else None,
src_id=self.INITIATOR_ID,
create_flags=ibv_qp_create_flags.IBV_QP_CREATE_SOURCE_QPN,
- source_qpn=(ri << 12),
+ source_qpn=(ri << 12) | self.pid_on_fep,
send_ops_flags=ibv_qp_create_send_ops_flags.IBV_QP_EX_WITH_SEND)

def test_create_ru_qp(self):
@@ -271,25 +274,18 @@ class RuQPTest(RuAPITestCase):
with self.assertRaises(PyverbsRDMAError):
QPEx(self.ctx, self.qp_init_attr(jkey=False))

- def test_explicit_qpn_zero(self):
+ def test_explicit_resource_index_zero(self):
"""
- Zero is a queue pair number like any other, and asking for it must
- get it rather than an assigned one.
+ Resource index zero is valid and must not be mistaken for an
+ unrequested source QPN.

The signal that a QPN was requested is IBV_QP_CREATE_SOURCE_QPN in
- create_flags, not a non-zero source_qpn - both the library and the
- driver once inferred it from the value and so silently ignored a
- request for RI 0, PIDonFEP 0.
-
- PIDonFEP is a device-wide space, so this needs to be the only queue
- pair claiming zero while it runs; every test here builds its own
- protection domain and releases it, which is what keeps that true.
+ create_flags, not whether source_qpn is zero.
"""
- attr = self.qp_init_attr()
- attr.source_qpn = 0
+ attr = self.qp_init_attr(res_index=0)
with QPEx(self.ctx, attr) as qp:
- self.assertEqual(qp.qp_num, 0,
- 'asked for QPN 0 and was assigned one instead')
+ self.assertEqual(qp.qp_num, self.pid_on_fep,
+ 'asked for resource index zero and got another')

def test_res_index_is_exclusive(self):
"""
@@ -329,11 +325,8 @@ class RuQPTest(RuAPITestCase):

def test_modify_rts_to_err_to_reset(self):
"""
- The full lifecycle this device supports: an established pair can be
- flushed to ERROR, reset, and put back to work without being
- destroyed. ERROR is reachable from anywhere that is not already
- ERROR - a queue pair being taken out of service should not first
- have to be in the right state to be taken out of service.
+ An RU QP can be flushed to ERROR, reset, and returned to RTS. RESET
+ cannot transition directly to ERROR.
"""
with QPEx(self.ctx, self.qp_init_attr()) as qp:
self.assertEqual(self.cur_state(qp), ibv_qp_state.IBV_QPS_RTS,
@@ -349,8 +342,8 @@ class RuQPTest(RuAPITestCase):
self.assertEqual(self.to_state(qp, ibv_qp_state.IBV_QPS_RESET), 0)
self.assertEqual(self.cur_state(qp), ibv_qp_state.IBV_QPS_RESET)

- self.assertEqual(self.to_state(qp, ibv_qp_state.IBV_QPS_ERR), 0,
- 'ERROR reaches from anywhere')
+ self.assertEqual(self.to_state(qp, ibv_qp_state.IBV_QPS_ERR), errno.EINVAL,
+ 'RESET cannot transition directly to ERROR')
self.assertEqual(self.to_state(qp, ibv_qp_state.IBV_QPS_RESET), 0)
self.assertEqual(self.to_state(qp, ibv_qp_state.IBV_QPS_RTS), 0)
self.assertEqual(self.cur_state(qp), ibv_qp_state.IBV_QPS_RTS)
@@ -358,41 +351,35 @@ class RuQPTest(RuAPITestCase):
def test_modify_to_init_and_rtr_refused(self):
"""
INIT and RTR exist to negotiate a connection. An RU queue pair has
- nothing to negotiate, so both are refused - with EOPNOTSUPP, saying
- the device does not have the state rather than that the request was
- malformed - and the queue pair is left where it was.
+ nothing to negotiate, so both transitions are refused and the queue
+ pair is left where it was.
"""
with QPEx(self.ctx, self.qp_init_attr()) as qp:
for state in (ibv_qp_state.IBV_QPS_INIT,
ibv_qp_state.IBV_QPS_RTR):
- self.assertEqual(self.to_state(qp, state), errno.EOPNOTSUPP)
+ self.assertNotEqual(self.to_state(qp, state), 0)
self.assertEqual(self.cur_state(qp), ibv_qp_state.IBV_QPS_RTS)

def test_modify_in_place_applies_attrs(self):
"""
- A modify from RTS to RTS is not a no-op: fields carried with it are
- applied, and read back.
+ A supported port attribute can be carried with an RTS-to-RTS modify.
"""
with QPEx(self.ctx, self.qp_init_attr()) as qp:
attr = QPAttr(port_num=self.ib_port)
attr.qp_state = ibv_qp_state.IBV_QPS_RTS
- attr.qp_access_flags = ibv_access_flags.IBV_ACCESS_REMOTE_READ | \
- ibv_access_flags.IBV_ACCESS_REMOTE_WRITE
qp.modify(attr, ibv_qp_attr_mask.IBV_QP_STATE |
- ibv_qp_attr_mask.IBV_QP_ACCESS_FLAGS)
- queried, _ = qp.query(ibv_qp_attr_mask.IBV_QP_ACCESS_FLAGS)
- self.assertEqual(queried.qp_access_flags, attr.qp_access_flags)
+ ibv_qp_attr_mask.IBV_QP_PORT)
+ queried, _ = qp.query(ibv_qp_attr_mask.IBV_QP_PORT)
+ self.assertEqual(queried.port_num, self.ib_port)

- def test_modify_without_state_refused(self):
+ def test_modify_port_without_state_preserves_state(self):
"""
- State is mandatory. A modify that names only a port is refused with
- EINVAL rather than quietly doing half of what was asked.
+ Port-only updates do not change the current QP state.
"""
with QPEx(self.ctx, self.qp_init_attr()) as qp:
attr = QPAttr(port_num=self.ib_port)
- with self.assertRaises(PyverbsRDMAError) as ctx:
- qp.modify(attr, ibv_qp_attr_mask.IBV_QP_PORT)
- self.assertEqual(ctx.exception.error_code, errno.EINVAL)
+ qp.modify(attr, ibv_qp_attr_mask.IBV_QP_PORT)
+ self.assertEqual(self.cur_state(qp), ibv_qp_state.IBV_QPS_RTS)

def test_pidonfep_is_latched_per_pd(self):
"""
@@ -403,7 +390,7 @@ class RuQPTest(RuAPITestCase):
"""
with QPEx(self.ctx, self.qp_init_attr()):
attr = self.qp_init_attr(res_index=self.RES_INDEX + 1)
- attr.source_qpn = ((self.RES_INDEX + 1) << 12) | 1
+ attr.source_qpn = ((self.RES_INDEX + 1) << 12) | (self.pid_on_fep ^ 1)
with self.assertRaises(PyverbsRDMAError):
QPEx(self.ctx, attr)

@@ -441,6 +428,8 @@ class RuQPTest(RuAPITestCase):
if ex.error_code == errno.EOPNOTSUPP:
raise unittest.SkipTest('Query QP semantics is not supported')
raise ex
+ if not sem.max_pdu:
+ raise unittest.SkipTest('Running kernel does not report RU max_pdu')
self.assertGreater(sem.max_pdu, 0)
# the payload MTU the port reports and the one the semantics report
# are the same number arrived at two ways; a device that disagrees
@@ -471,16 +460,10 @@ class RuPDTest(RuAPITestCase):

def test_pd_exhaustion(self):
"""
- One past the id space must fail rather than succeed quietly.
+ CXI PDs are software references to a shared LNI, not separately
+ allocated device IDs with a testable exhaustion boundary.
"""
- pds = [PD(self.ctx) for _ in range(self.MAX_PDS)]
- try:
- with self.assertRaises(PyverbsRDMAError) as cm:
- PD(self.ctx)
- self.assertEqual(cm.exception.error_code, errno.ENOSPC)
- finally:
- for pd in pds:
- pd.close()
+ raise unittest.SkipTest('CXI PDs do not consume a bounded device PD pool')


class RuMRTest(RuAPITestCase):
@@ -602,12 +585,16 @@ class RuDeviceTest(RuAPITestCase):
ibv_gid_type_sysfs.IBV_GID_TYPE_SYSFS_UET_IP)
port = self.ctx.query_port(self.ib_port)
self.assertGreater(port.gid_tbl_len, 0)
+ gid_types = []
for idx in range(port.gid_tbl_len):
gid = self.ctx.query_gid(self.ib_port, idx)
if gid.gid[-19:] == '0000:0000:0000:0000':
continue
- self.assertIn(self.ctx.query_gid_type(self.ib_port, idx),
- uet_types, f'GID {idx} is not a UET GID type')
+ gid_types.append(self.ctx.query_gid_type(self.ib_port, idx))
+ if not any(gid_type in uet_types for gid_type in gid_types):
+ raise unittest.SkipTest('Running kernel does not expose UET GID types')
+ for idx, gid_type in enumerate(gid_types):
+ self.assertIn(gid_type, uet_types, f'GID {idx} is not a UET GID type')

def test_port_is_ethernet(self):
# UET rides UDP over IP, so the port is an Ethernet link layer
@@ -638,7 +625,8 @@ class RuTrafficTest(RuRDMATestCase):
send_op=ibv_wr_opcode.IBV_WR_SEND)

def test_uet_send_imm(self):
- self.create_players()
+ self.create_players(
+ send_ops_flags=ibv_qp_create_send_ops_flags.IBV_QP_EX_WITH_SEND_WITH_IMM)
u.traffic(**self.traffic_args, new_send=True,
send_op=ibv_wr_opcode.IBV_WR_SEND_WITH_IMM)

@@ -665,6 +653,6 @@ class RuTrafficTest(RuRDMATestCase):

def test_uet_rdma_write_imm(self):
self.create_players(
- send_ops_flags=ibv_qp_create_send_ops_flags.IBV_QP_EX_WITH_RDMA_WRITE_WITH_IMM)
+ send_ops_flags=ibv_qp_create_send_ops_flags.IBV_QP_EX_WITH_RDMA_WRITE64_WITH_IMM)
u.traffic(**self.traffic_args, new_send=True,
send_op=ibv_wr_opcode.IBV_WR_RDMA_WRITE_WITH_IMM)

--
2.56.0