summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:22:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:22:29 +0000
commit068a45420f2c98887e220b45e946cc7074da550e (patch)
treec5b54e8b4b235232b057a9c534d9a16d2208463d /test
parentInitial commit. (diff)
downloadlibnvme-068a45420f2c98887e220b45e946cc7074da550e.tar.xz
libnvme-068a45420f2c98887e220b45e946cc7074da550e.zip
Adding upstream version 1.8.upstream/1.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--test/cpp.cc66
-rw-r--r--test/ioctl/discovery.c428
-rw-r--r--test/ioctl/features.c1604
-rw-r--r--test/ioctl/identify.c572
-rw-r--r--test/ioctl/meson.build42
-rw-r--r--test/ioctl/mock.c174
-rw-r--r--test/ioctl/mock.h104
-rw-r--r--test/ioctl/util.c65
-rw-r--r--test/ioctl/util.h19
-rw-r--r--test/meson.build103
-rw-r--r--test/mi-mctp.c780
-rw-r--r--test/mi.c1937
-rw-r--r--test/mock-ifaddrs.c123
-rw-r--r--test/nbft/README17
-rw-r--r--test/nbft/diffs/NBFT-Dell.PowerEdge.R660-fw1.5.5-single54
-rw-r--r--test/nbft/diffs/NBFT-Dell.PowerEdge.R76060
-rw-r--r--test/nbft/diffs/NBFT-auto-ipv638
-rw-r--r--test/nbft/diffs/NBFT-dhcp-ipv443
-rw-r--r--test/nbft/diffs/NBFT-dhcp-ipv638
-rw-r--r--test/nbft/diffs/NBFT-rhpoc38
-rw-r--r--test/nbft/diffs/NBFT-static-ipv438
-rw-r--r--test/nbft/diffs/NBFT-static-ipv4-discovery43
-rw-r--r--test/nbft/diffs/NBFT-static-ipv638
-rwxr-xr-xtest/nbft/gen-nbft-diffs.sh.in5
-rw-r--r--test/nbft/meson.build85
-rwxr-xr-xtest/nbft/nbft-dump-diff.sh.in8
-rw-r--r--test/nbft/nbft-dump.c121
-rw-r--r--test/nbft/tables/NBFT-Dell.PowerEdge.R660-fw1.5.5-singlebin0 -> 930 bytes
l---------test/nbft/tables/NBFT-Dell.PowerEdge.R7601
-rw-r--r--test/nbft/tables/NBFT-auto-ipv6bin0 -> 721 bytes
-rw-r--r--test/nbft/tables/NBFT-dhcp-ipv4bin0 -> 825 bytes
-rw-r--r--test/nbft/tables/NBFT-dhcp-ipv6bin0 -> 725 bytes
-rw-r--r--test/nbft/tables/NBFT-rhpocbin0 -> 724 bytes
-rw-r--r--test/nbft/tables/NBFT-static-ipv4bin0 -> 725 bytes
-rw-r--r--test/nbft/tables/NBFT-static-ipv4-discoverybin0 -> 825 bytes
-rw-r--r--test/nbft/tables/NBFT-static-ipv6bin0 -> 721 bytes
-rw-r--r--test/nbft/tables_bad/NBFT-bad-oldspecbin0 -> 1103 bytes
-rw-r--r--test/nbft/tables_bad/NBFT-random-noisebin0 -> 512 bytes
-rw-r--r--test/register.c233
-rw-r--r--test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out32
-rw-r--r--test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xzbin0 -> 19712 bytes
-rw-r--r--test/sysfs/meson.build29
-rwxr-xr-xtest/sysfs/setup.sh11
-rw-r--r--test/sysfs/sysfs.c89
-rw-r--r--test/test-util.c121
-rw-r--r--test/test.c437
-rw-r--r--test/tree.c1184
-rw-r--r--test/tree.py23
-rw-r--r--test/utils.c68
-rw-r--r--test/utils.h20
-rw-r--r--test/uuid.c120
-rw-r--r--test/zns.c90
52 files changed, 9101 insertions, 0 deletions
diff --git a/test/cpp.cc b/test/cpp.cc
new file mode 100644
index 0000000..3d0a7d2
--- /dev/null
+++ b/test/cpp.cc
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors: Keith Busch <keith.busch@wdc.com>
+ */
+
+#include <iostream>
+#include <libnvme.h>
+
+int main()
+{
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ nvme_path_t p;
+ nvme_ns_t n;
+
+ r = nvme_scan(NULL);
+ if (!r)
+ return -1;
+
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ std::cout << nvme_subsystem_get_name(s)
+ << " - NQN=" << nvme_subsystem_get_nqn(s)
+ << "\n";
+ nvme_subsystem_for_each_ctrl(s, c) {
+ std::cout << " `- " << nvme_ctrl_get_name(c)
+ << " " << nvme_ctrl_get_transport(c)
+ << " " << nvme_ctrl_get_address(c)
+ << " " << nvme_ctrl_get_state(c)
+ << "\n";
+ nvme_ctrl_for_each_ns(c, n) {
+ std::cout << " `- "
+ << nvme_ns_get_name(n)
+ << "lba size:"
+ << nvme_ns_get_lba_size(n)
+ << " lba max:"
+ << nvme_ns_get_lba_count(n)
+ << "\n";
+ }
+ nvme_ctrl_for_each_path(c, p) {
+ std::cout << " `- "
+ << nvme_path_get_name(p)
+ << " "
+ << nvme_path_get_ana_state(p)
+ << "\n";
+ }
+ }
+ nvme_subsystem_for_each_ns(s, n) {
+ std::cout << " `- " << nvme_ns_get_name(n)
+ << "lba size:"
+ << nvme_ns_get_lba_size(n)
+ << " lba max:"
+ << nvme_ns_get_lba_count(n) << "\n";
+ }
+ }
+ }
+ std::cout << "\n";
+ nvme_free_tree(r);
+
+ return 0;
+}
diff --git a/test/ioctl/discovery.c b/test/ioctl/discovery.c
new file mode 100644
index 0000000..f5f6f51
--- /dev/null
+++ b/test/ioctl/discovery.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+#include "../../src/nvme/private.h"
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define HEADER_LEN 20
+
+static void arbitrary_ascii_string(size_t max_len, char *str, char *log_str)
+{
+ size_t len;
+ size_t i;
+
+ len = arbitrary_range(max_len + 1);
+ for (i = 0; i < len; i++) {
+ /*
+ * ASCII strings shall contain only code values 20h through 7Eh.
+ * Exclude 20h (space) because it ends the string.
+ */
+ str[i] = log_str[i] = arbitrary_range(0x7E - 0x20) + 0x20 + 1;
+ }
+ for (i = len; i < max_len; i++) {
+ str[i] = '\0';
+ log_str[i] = ' ';
+ }
+}
+
+static void arbitrary_entry(struct nvmf_disc_log_entry *entry,
+ struct nvmf_disc_log_entry *log_entry)
+{
+ arbitrary(entry, sizeof(*entry));
+ memcpy(log_entry, entry, sizeof(*entry));
+ arbitrary_ascii_string(
+ sizeof(entry->trsvcid), entry->trsvcid, log_entry->trsvcid);
+ arbitrary_ascii_string(
+ sizeof(entry->traddr), entry->traddr, log_entry->traddr);
+}
+
+static void arbitrary_entries(size_t len,
+ struct nvmf_disc_log_entry *entries,
+ struct nvmf_disc_log_entry *log_entries)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ arbitrary_entry(&entries[i], &log_entries[i]);
+}
+
+static void test_no_entries(nvme_ctrl_t c)
+{
+ struct nvmf_discovery_log header = {};
+ /* No entries to fetch after fetching the header */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ free(log);
+}
+
+static void test_four_entries(nvme_ctrl_t c)
+{
+ size_t num_entries = 4;
+ struct nvmf_disc_log_entry entries[num_entries];
+ struct nvmf_disc_log_entry log_entries[num_entries];
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)};
+ /*
+ * All 4 entries should be fetched at once
+ * followed by the header again (to ensure genctr hasn't changed)
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries),
+ .cdw10 = (sizeof(entries) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = log_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary_entries(num_entries, entries, log_entries);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ cmp(log->entries, entries, sizeof(entries), "incorrect entries");
+ free(log);
+}
+
+static void test_five_entries(nvme_ctrl_t c)
+{
+ size_t num_entries = 5;
+ struct nvmf_disc_log_entry entries[num_entries];
+ struct nvmf_disc_log_entry log_entries[num_entries];
+ size_t first_entries = 4;
+ size_t first_data_len = first_entries * sizeof(*entries);
+ size_t second_entries = num_entries - first_entries;
+ size_t second_data_len = second_entries * sizeof(*entries);
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(num_entries)};
+ /*
+ * The first 4 entries (4 KB) are fetched together,
+ * followed by last entry separately.
+ * Finally, the header is fetched again to check genctr.
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = first_data_len,
+ .cdw10 = (first_data_len / 4 - 1) << 16 /* NUMDL */
+ | 1 << 15 /* RAE */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = log_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = second_data_len,
+ .cdw10 = (second_data_len / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header) + first_data_len, /* LPOL */
+ .out_data = log_entries + first_entries,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary_entries(num_entries, entries, log_entries);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header, sizeof(header), "incorrect header");
+ cmp(log->entries, entries, sizeof(entries), "incorrect entries");
+ free(log);
+}
+
+static void test_genctr_change(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entries1[1];
+ struct nvmf_discovery_log header1 = {
+ .numrec = cpu_to_le64(ARRAY_SIZE(entries1)),
+ };
+ size_t num_entries2 = 2;
+ struct nvmf_disc_log_entry entries2[num_entries2];
+ struct nvmf_disc_log_entry log_entries2[num_entries2];
+ struct nvmf_discovery_log header2 = {
+ .genctr = cpu_to_le64(1),
+ .numrec = cpu_to_le64(num_entries2),
+ };
+ /*
+ * genctr changes after the entries are fetched the first time,
+ * so the log page entries are refetched
+ */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries1),
+ .cdw10 = (sizeof(entries1) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* NUMDL */
+ .cdw12 = sizeof(header1), /* LPOL */
+ .out_data = entries1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entries2),
+ .cdw10 = (sizeof(entries2) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header2), /* LPOL */
+ .out_data = log_entries2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(entries1, sizeof(entries1));
+ arbitrary_entries(num_entries2, entries2, log_entries2);
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 2) == 0, "discovery failed: %m");
+ end_mock_cmds();
+ cmp(log, &header2, sizeof(header2), "incorrect header");
+ cmp(log->entries, entries2, sizeof(entries2), "incorrect entries");
+ free(log);
+}
+
+static void test_max_retries(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entry;
+ struct nvmf_discovery_log header1 = {.numrec = cpu_to_le64(1)};
+ struct nvmf_discovery_log header2 = {
+ .genctr = cpu_to_le64(1),
+ .numrec = cpu_to_le64(1),
+ };
+ struct nvmf_discovery_log header3 = {
+ .genctr = cpu_to_le64(2),
+ .numrec = cpu_to_le64(1),
+ };
+ /* genctr changes in both attempts, hitting the max retries (2) */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header1,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header1), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header2,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header2), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header3,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(&entry, sizeof(entry));
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 2) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(errno == EAGAIN, "discovery failed: %m");
+ check(!log, "unexpected log page returned");
+}
+
+static void test_header_error(nvme_ctrl_t c)
+{
+ /* Stop after an error in fetching the header the first time */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .err = NVME_SC_INVALID_OPCODE,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(!log, "unexpected log page returned");
+}
+
+static void test_entries_error(nvme_ctrl_t c)
+{
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)};
+ size_t entry_size = sizeof(struct nvmf_disc_log_entry);
+ /* Stop after an error in fetching the entries */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = entry_size,
+ .cdw10 = (entry_size / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .err = -EIO,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(errno == EIO, "discovery failed: %m");
+ check(!log, "unexpected log page returned");
+}
+
+static void test_genctr_error(nvme_ctrl_t c)
+{
+ struct nvmf_disc_log_entry entry;
+ struct nvmf_discovery_log header = {.numrec = cpu_to_le64(1)};
+ /* Stop after an error in refetching the header */
+ struct mock_cmd mock_admin_cmds[] = {
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .out_data = &header,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = sizeof(entry),
+ .cdw10 = (sizeof(entry) / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .cdw12 = sizeof(header), /* LPOL */
+ .out_data = &entry,
+ },
+ {
+ .opcode = nvme_admin_get_log_page,
+ .data_len = HEADER_LEN,
+ .cdw10 = (HEADER_LEN / 4 - 1) << 16 /* NUMDL */
+ | NVME_LOG_LID_DISCOVER, /* LID */
+ .err = NVME_SC_INTERNAL,
+ },
+ };
+ struct nvmf_discovery_log *log = NULL;
+
+ arbitrary(&entry, sizeof(entry));
+ set_mock_admin_cmds(mock_admin_cmds, ARRAY_SIZE(mock_admin_cmds));
+ check(nvmf_get_discovery_log(c, &log, 1) == -1, "discovery succeeded");
+ end_mock_cmds();
+ check(!log, "unexpected log page returned");
+}
+
+static void run_test(const char *test_name, void (*test_fn)(nvme_ctrl_t))
+{
+ struct nvme_ctrl c = {.fd = TEST_FD};
+
+ printf("Running test %s...", test_name);
+ fflush(stdout);
+ check(asprintf(&c.name, "%s_ctrl", test_name) >= 0, "asprintf() failed");
+ test_fn(&c);
+ free(c.name);
+ puts(" OK");
+}
+
+#define RUN_TEST(name) run_test(#name, test_ ## name)
+
+int main(void)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(no_entries);
+ RUN_TEST(four_entries);
+ RUN_TEST(five_entries);
+ RUN_TEST(genctr_change);
+ RUN_TEST(max_retries);
+ RUN_TEST(header_error);
+ RUN_TEST(entries_error);
+ RUN_TEST(genctr_error);
+}
diff --git a/test/ioctl/features.c b/test/ioctl/features.c
new file mode 100644
index 0000000..7386497
--- /dev/null
+++ b/test/ioctl/features.c
@@ -0,0 +1,1604 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define TEST_TIMEOUT 1234
+#define TEST_NSID 0x89ABCDEF
+#define TEST_CDW11 0x11111111
+#define TEST_CDW12 0x12121212
+#define TEST_CDW13 0x13131313
+#define TEST_CDW15 0x15151515
+#define TEST_UUIDX 0b1001110
+#define TEST_FID 0xFE
+#define TEST_RESULT 0x12345678
+#define TEST_SEL NVME_GET_FEATURES_SEL_SAVED
+#define TEST_SC NVME_SC_INVALID_FIELD
+
+static void test_set_features(void)
+{
+ uint32_t result = 0;
+ uint8_t data[256];
+ struct nvme_set_features_args args = {
+ .result = &result,
+ .data = data,
+ .args_size = sizeof(args),
+ .fd = TEST_FD,
+ .timeout = TEST_TIMEOUT,
+ .nsid = TEST_NSID,
+ .cdw11 = TEST_CDW11,
+ .cdw12 = TEST_CDW12,
+ .cdw13 = TEST_CDW13,
+ .cdw15 = TEST_CDW15,
+ .data_len = sizeof(data),
+ .save = true,
+ .uuidx = TEST_UUIDX,
+ .fid = TEST_FID,
+ };
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = data,
+ .data_len = sizeof(data),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .cdw12 = TEST_CDW12,
+ .cdw13 = TEST_CDW13,
+ .cdw14 = TEST_UUIDX,
+ .cdw15 = TEST_CDW15,
+ .timeout_ms = TEST_TIMEOUT,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features(&args);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features(void)
+{
+ uint32_t result = 0;
+ uint8_t data[256], get_data[sizeof(data)] = {};
+ struct nvme_get_features_args args = {
+ .result = &result,
+ .data = get_data,
+ .args_size = sizeof(args),
+ .fd = TEST_FD,
+ .timeout = TEST_TIMEOUT,
+ .nsid = TEST_NSID,
+ .sel = TEST_SEL,
+ .cdw11 = TEST_CDW11,
+ .data_len = sizeof(data),
+ .fid = TEST_FID,
+ .uuidx = TEST_UUIDX,
+ };
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(data),
+ .cdw10 = TEST_SEL << 8 | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .cdw14 = TEST_UUIDX,
+ .timeout_ms = TEST_TIMEOUT,
+ .out_data = data,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features(&args);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(get_data, data, sizeof(data), "incorrect data");
+}
+
+static void test_set_features_data(void)
+{
+ uint8_t data[128];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = data,
+ .data_len = sizeof(data),
+ .cdw10 = TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_data(
+ TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, false,
+ sizeof(data), data, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features_data(void)
+{
+ uint8_t data[128], get_data[sizeof(data)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(data),
+ .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID,
+ .out_data = data,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(data, sizeof(data));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_data(
+ TEST_FD, TEST_FID, TEST_NSID, sizeof(data), get_data, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(get_data, data, sizeof(data), "incorrect data");
+}
+
+static void test_set_features_simple(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | TEST_FID,
+ .cdw11 = TEST_CDW11,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_simple(
+ TEST_FD, TEST_FID, TEST_NSID, TEST_CDW11, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_features_simple(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_GET_FEATURES_SEL_CURRENT << 8 | TEST_FID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_simple(TEST_FD, TEST_FID, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_arbitration(void)
+{
+ uint8_t HPW = 0xAA, MPW = 0xBB, LPW = 0xCC, AB = 0b111;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_ARBITRATION,
+ .cdw11 = (uint32_t)HPW << 24 | MPW << 16 | LPW << 8 | AB,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_arbitration(
+ TEST_FD, AB, LPW, MPW, HPW, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_arbitration(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ARBITRATION,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_arbitration(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_power_mgmt(void)
+{
+ uint8_t PS = 0b10101, WH = 0b101;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_POWER_MGMT,
+ .cdw11 = WH << 5 | PS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_power_mgmt(TEST_FD, PS, WH, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_power_mgmt(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_POWER_MGMT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_power_mgmt(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_lba_range(void)
+{
+ uint8_t NUM = 64;
+ struct nvme_lba_range_type range_types;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .in_data = &range_types,
+ .data_len = sizeof(range_types),
+ .cdw10 = NVME_FEAT_FID_LBA_RANGE,
+ .cdw11 = NUM - 1,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&range_types, sizeof(range_types));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_lba_range(
+ TEST_FD, TEST_NSID, NUM, false, &range_types, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_lba_range(void)
+{
+ struct nvme_lba_range_type range_types, get_range_types = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(range_types),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_RANGE,
+ .out_data = &range_types,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&range_types, sizeof(range_types));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_lba_range2(
+ TEST_FD, TEST_SEL, TEST_NSID, &get_range_types, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_range_types, &range_types, sizeof(range_types),
+ "incorrect LBA range types");
+}
+
+static void test_set_temp_thresh(void)
+{
+ uint16_t TMPTH = 0xFEDC;
+ uint8_t TMPSEL = 0x8;
+ enum nvme_feat_tmpthresh_thsel THSEL =
+ NVME_FEATURE_TEMPTHRESH_THSEL_UNDER;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_TEMP_THRESH,
+ .cdw11 = THSEL << 20 | TMPSEL << 16 | TMPTH,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_temp_thresh(
+ TEST_FD, TMPTH, TMPSEL, THSEL, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_temp_thresh(void)
+{
+ /*
+ * nvme_get_features_temp_thresh() doesn't support
+ * specifying TMPSEL and THSEL
+ */
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TEMP_THRESH,
+ .cdw11 = NVME_FEATURE_TEMPTHRESH_THSEL_OVER << 20,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_temp_thresh(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_err_recovery(void)
+{
+ uint16_t TLER = 0xCDEF;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_ERR_RECOVERY,
+ .cdw11 = 1 << 16 /* DULBE */
+ | TLER,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_err_recovery(
+ TEST_FD, TEST_NSID, TLER, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_err_recovery(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ERR_RECOVERY,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_err_recovery2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_volatile_wc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_VOLATILE_WC,
+ .cdw11 = 1 << 0, /* WCE */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_volatile_wc(TEST_FD, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_volatile_wc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8
+ | NVME_FEAT_FID_VOLATILE_WC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_volatile_wc(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_num_queues(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_irq_coalesce(void)
+{
+ uint8_t THR = 0xAB, TIME = 0xCD;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_IRQ_COALESCE,
+ .cdw11 = TIME << 8 | THR,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_irq_coalesce(
+ TEST_FD, THR, TIME, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_irq_coalesce(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_COALESCE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_irq_coalesce(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_irq_config(void)
+{
+ uint16_t IV = 0x1234;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_IRQ_CONFIG,
+ .cdw11 = 1 << 16 /* CD */
+ | IV,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_irq_config(TEST_FD, IV, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_irq_config(void)
+{
+ uint16_t IV = 0x5678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IRQ_CONFIG,
+ .cdw11 = IV,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_irq_config(TEST_FD, TEST_SEL, IV, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_write_atomic(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_WRITE_ATOMIC,
+ .cdw11 = 1 << 0, /* DN */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_write_atomic(TEST_FD, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_write_atomic(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_ATOMIC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_write_atomic(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_async_event(void)
+{
+ uint32_t EVENTS = 0x87654321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_ASYNC_EVENT,
+ .cdw11 = EVENTS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_async_event(TEST_FD, EVENTS, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_async_event(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ASYNC_EVENT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_async_event(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_auto_pst(void)
+{
+ struct nvme_feat_auto_pst apst;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &apst,
+ .data_len = sizeof(apst),
+ .cdw10 = NVME_FEAT_FID_AUTO_PST,
+ .cdw11 = 1 << 0, /* APSTE */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&apst, sizeof(apst));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_auto_pst(TEST_FD, true, false, &apst, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_auto_pst(void)
+{
+ struct nvme_feat_auto_pst apst, get_apst = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(apst),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_AUTO_PST,
+ .out_data = &apst,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&apst, sizeof(apst));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_auto_pst(TEST_FD, TEST_SEL, &get_apst, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_apst, &apst, sizeof(apst), "incorrect apst");
+}
+
+static void test_get_host_mem_buf(void)
+{
+ struct nvme_host_mem_buf_attrs attrs, get_attrs = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(attrs),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_MEM_BUF,
+ .out_data = &attrs,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&attrs, sizeof(attrs));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_mem_buf2(
+ TEST_FD, TEST_SEL, &get_attrs, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_attrs, &attrs, sizeof(attrs), "incorrect attrs");
+}
+
+static void test_set_timestamp(void)
+{
+ struct nvme_timestamp ts = {.timestamp = {1, 2, 3, 4, 5, 6}};
+ uint64_t timestamp = ts.timestamp[0]
+ | (uint64_t) ts.timestamp[1] << 8
+ | (uint64_t) ts.timestamp[2] << 16
+ | (uint64_t) ts.timestamp[3] << 24
+ | (uint64_t) ts.timestamp[4] << 32
+ | (uint64_t) ts.timestamp[5] << 40;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &ts,
+ .data_len = sizeof(ts),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_TIMESTAMP,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_timestamp(TEST_FD, true, timestamp);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_timestamp(void)
+{
+ struct nvme_timestamp ts, get_ts = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(ts),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_TIMESTAMP,
+ .out_data = &ts,
+ };
+ int err;
+
+ arbitrary(&ts, sizeof(ts));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_timestamp(TEST_FD, TEST_SEL, &get_ts);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(&get_ts, &ts, sizeof(ts), "incorrect timestamp");
+}
+
+static void test_get_kato(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_hctm(void)
+{
+ uint16_t TMT2 = 0x4321, TMT1 = 0x8765;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_HCTM,
+ .cdw11 = (uint32_t)TMT1 << 16 | TMT2,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_hctm(TEST_FD, TMT2, TMT1, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_hctm(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HCTM,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_hctm(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_nopsc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_NOPSC,
+ .cdw11 = 1 << 0 /* NOPPME */,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_nopsc(TEST_FD, true, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_nopsc(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NOPSC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_nopsc(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_rrl(void)
+{
+ uint8_t RRL = 0xA;
+ uint16_t NVMSETID = 0x1234;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_RRL,
+ .cdw11 = NVMSETID,
+ .cdw12 = RRL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_rrl(TEST_FD, RRL, NVMSETID, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_rrl(void)
+{
+ /* nvme_get_features_rrl() doesn't support specifying the NVMSETID */
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RRL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_rrl(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_plm_config(void)
+{
+ uint16_t NVMSETID = 0xFEDC;
+ struct nvme_plm_config config;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &config,
+ .data_len = sizeof(config),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_PLM_CONFIG,
+ .cdw11 = NVMSETID,
+ .cdw12 = 1 << 0 /* Predictable Latency Enable */,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&config, sizeof(config));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_plm_config(
+ TEST_FD, true, NVMSETID, true, &config, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_plm_config(void)
+{
+ uint16_t NVMSETID = 0xABCD;
+ struct nvme_plm_config config, get_config = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(config),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_CONFIG,
+ .cdw11 = NVMSETID,
+ .out_data = &config,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&config, sizeof(config));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_plm_config(
+ TEST_FD, TEST_SEL, NVMSETID, &get_config, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_config, &config, sizeof(config), "incorrect PLM config");
+}
+
+static void test_set_plm_window(void)
+{
+ enum nvme_feat_plm_window_select SEL = NVME_FEATURE_PLM_NDWIN;
+ uint16_t NVMSETID = 0x4321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_PLM_WINDOW,
+ .cdw11 = NVMSETID,
+ .cdw12 = SEL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_plm_window(
+ TEST_FD, SEL, NVMSETID, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_plm_window(void)
+{
+ uint16_t NVMSETID = 0x8765;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_PLM_WINDOW,
+ .cdw11 = NVMSETID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_plm_window(
+ TEST_FD, TEST_SEL, NVMSETID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_lba_sts_interval(void)
+{
+ uint16_t LSIRI = 0x1234, LSIPI = 0x5678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_LBA_STS_INTERVAL,
+ .cdw11 = LSIPI << 16 | LSIRI,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_lba_sts_interval(
+ TEST_FD, LSIRI, LSIPI, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_lba_sts_interval(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_LBA_STS_INTERVAL,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_lba_sts_interval(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_host_behavior(void)
+{
+ /* nvme_set_features_host_behavior() ignores SAVE */
+ struct nvme_feat_host_behavior behavior;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = &behavior,
+ .data_len = sizeof(behavior),
+ .cdw10 = NVME_FEAT_FID_HOST_BEHAVIOR,
+ };
+ int err;
+
+ arbitrary(&behavior, sizeof(behavior));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_behavior(TEST_FD, true, &behavior);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_host_behavior(void)
+{
+ struct nvme_feat_host_behavior behavior, get_behavior = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(behavior),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_BEHAVIOR,
+ .out_data = &behavior,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ arbitrary(&behavior, sizeof(behavior));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_behavior(
+ TEST_FD, TEST_SEL, &get_behavior, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+ cmp(&get_behavior, &behavior, sizeof(behavior), "incorrect behavior");
+}
+
+static void test_set_sanitize(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_SANITIZE,
+ .cdw11 = 1 << 0, /* NODRM */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_sanitize(TEST_FD, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_sanitize(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SANITIZE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_sanitize(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_endurance_evt_cfg(void)
+{
+ uint16_t ENDGID = 0x9876;
+ uint8_t EGWARN = 0xCD;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_ENDURANCE_EVT_CFG,
+ .cdw11 = EGWARN << 16 | ENDGID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_endurance_evt_cfg(
+ TEST_FD, ENDGID, EGWARN, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_endurance_event_cfg(void)
+{
+ uint16_t ENDGID = 0x6789;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_ENDURANCE_EVT_CFG,
+ .cdw11 = ENDGID,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_endurance_event_cfg(
+ TEST_FD, TEST_SEL, ENDGID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_iocs_profile(void)
+{
+ uint16_t IOCSI = 0b101100111;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_IOCS_PROFILE,
+ .cdw11 = IOCSI,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_iocs_profile(TEST_FD, IOCSI, false);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_iocs_profile(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_IOCS_PROFILE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_iocs_profile(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_sw_progress(void)
+{
+ uint8_t PBSLC = 0xBA;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_SW_PROGRESS,
+ .cdw11 = PBSLC,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_sw_progress(TEST_FD, PBSLC, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_sw_progress(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_SW_PROGRESS,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_sw_progress(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_host_id(void)
+{
+ uint8_t hostid[8];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = hostid,
+ .data_len = sizeof(hostid),
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_HOST_ID,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_id(TEST_FD, false, true, hostid);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_set_host_id_extended(void)
+{
+ uint8_t hostid[16];
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .in_data = hostid,
+ .data_len = sizeof(hostid),
+ .cdw10 = NVME_FEAT_FID_HOST_ID,
+ .cdw11 = 1 << 0, /* EXHID */
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_host_id(TEST_FD, true, false, hostid);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+}
+
+static void test_get_host_id(void)
+{
+ uint8_t hostid[8], get_hostid[sizeof(hostid)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(hostid),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID,
+ .out_data = hostid,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_id(
+ TEST_FD, TEST_SEL, false, sizeof(hostid), get_hostid);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier");
+}
+
+static void test_get_host_id_extended(void)
+{
+ uint8_t hostid[16], get_hostid[sizeof(hostid)] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .data_len = sizeof(hostid),
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_HOST_ID,
+ .cdw11 = 1 << 0, /* EXHID */
+ .out_data = hostid,
+ .result = TEST_RESULT,
+ };
+ int err;
+
+ arbitrary(hostid, sizeof(hostid));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_host_id(
+ TEST_FD, TEST_SEL, true, sizeof(hostid), get_hostid);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ cmp(get_hostid, hostid, sizeof(hostid), "incorrect host identifier");
+}
+
+static void test_set_resv_mask(void)
+{
+ uint32_t MASK = 0x23456789;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = (uint32_t)1 << 31 /* SAVE */
+ | NVME_FEAT_FID_RESV_MASK,
+ .cdw11 = MASK,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_mask2(
+ TEST_FD, TEST_NSID, MASK, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_resv_mask(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_MASK,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_resv_mask2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_resv_persist(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_RESV_PERSIST,
+ .cdw11 = 1 << 0, /* PTPL */
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_persist2(
+ TEST_FD, TEST_NSID, true, false, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_resv_persist(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_RESV_PERSIST,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_resv_persist2(
+ TEST_FD, TEST_SEL, TEST_NSID, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_write_protect(void)
+{
+ /* nvme_set_features_write_protect() ignores SAVE */
+ enum nvme_feat_nswpcfg_state STATE =
+ NVME_FEAT_NS_WRITE_PROTECT_PERMANENT;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_WRITE_PROTECT,
+ .cdw11 = STATE,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_write_protect2(
+ TEST_FD, TEST_NSID, STATE, true, &result);
+ end_mock_cmds();
+ check(err == 0, "set features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_write_protect(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .nsid = TEST_NSID,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_WRITE_PROTECT,
+ .result = TEST_RESULT,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_write_protect(
+ TEST_FD, TEST_NSID, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == 0, "get features returned error %d, errno %m", err);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+/*
+ * All set_features functions tail-call nvme_set_features(),
+ * so testing errors in any of them will do
+ */
+
+static void test_set_status_code_error(void)
+{
+ uint32_t EVENTS = 0x12345678;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .cdw10 = NVME_FEAT_FID_ASYNC_EVENT,
+ .cdw11 = EVENTS,
+ .result = TEST_RESULT,
+ .err = TEST_SC,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_async_event(TEST_FD, EVENTS, false, &result);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_set_kernel_error(void)
+{
+ uint32_t MASK = 0x87654321;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_set_features,
+ .nsid = TEST_NSID,
+ .cdw10 = NVME_FEAT_FID_RESV_MASK,
+ .cdw11 = MASK,
+ .result = TEST_RESULT,
+ .err = -EIO,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_set_features_resv_mask2(
+ TEST_FD, TEST_NSID, MASK, false, &result);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EIO, "unexpected error %m");
+ check(!result, "result unexpectedly set to %" PRIu32, result);
+}
+
+/*
+ * All get_features functions tail-call nvme_get_features(),
+ * so testing errors in any of them will do
+ */
+
+static void test_get_status_code_error(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_KATO,
+ .result = TEST_RESULT,
+ .err = TEST_SC,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_kato(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+ check(result == TEST_RESULT,
+ "got result %" PRIu32 ", expected %" PRIu32, result, TEST_RESULT);
+}
+
+static void test_get_kernel_error(void)
+{
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_get_features,
+ .cdw10 = TEST_SEL << 8 | NVME_FEAT_FID_NUM_QUEUES,
+ .result = TEST_RESULT,
+ .err = -EBUSY,
+ };
+ uint32_t result = 0;
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_get_features_num_queues(TEST_FD, TEST_SEL, &result);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EBUSY, "unexpected error %m");
+ check(!result, "result unexpectedly set to %" PRIu32, result);
+}
+
+static void run_test(const char *test_name, void (*test_fn)(void))
+{
+ printf("Running test %s...", test_name);
+ fflush(stdout);
+ test_fn();
+ puts(" OK");
+}
+
+#define RUN_TEST(name) run_test(#name, test_ ## name)
+
+int main(void)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(set_features);
+ RUN_TEST(get_features);
+ RUN_TEST(set_features_data);
+ RUN_TEST(get_features_data);
+ RUN_TEST(set_features_simple);
+ RUN_TEST(get_features_simple);
+ RUN_TEST(set_arbitration);
+ RUN_TEST(get_arbitration);
+ RUN_TEST(set_power_mgmt);
+ RUN_TEST(get_power_mgmt);
+ RUN_TEST(set_lba_range);
+ RUN_TEST(get_lba_range);
+ RUN_TEST(set_temp_thresh);
+ RUN_TEST(get_temp_thresh);
+ RUN_TEST(set_err_recovery);
+ RUN_TEST(get_err_recovery);
+ RUN_TEST(set_volatile_wc);
+ RUN_TEST(get_volatile_wc);
+ RUN_TEST(get_num_queues);
+ RUN_TEST(set_irq_coalesce);
+ RUN_TEST(get_irq_coalesce);
+ RUN_TEST(set_irq_config);
+ RUN_TEST(get_irq_config);
+ RUN_TEST(set_write_atomic);
+ RUN_TEST(get_write_atomic);
+ RUN_TEST(set_async_event);
+ RUN_TEST(get_async_event);
+ RUN_TEST(set_auto_pst);
+ RUN_TEST(get_auto_pst);
+ RUN_TEST(get_host_mem_buf);
+ RUN_TEST(set_timestamp);
+ RUN_TEST(get_timestamp);
+ RUN_TEST(get_kato);
+ RUN_TEST(set_hctm);
+ RUN_TEST(get_hctm);
+ RUN_TEST(set_nopsc);
+ RUN_TEST(get_nopsc);
+ RUN_TEST(set_rrl);
+ RUN_TEST(get_rrl);
+ RUN_TEST(set_plm_config);
+ RUN_TEST(get_plm_config);
+ RUN_TEST(set_plm_window);
+ RUN_TEST(get_plm_window);
+ RUN_TEST(set_lba_sts_interval);
+ RUN_TEST(get_lba_sts_interval);
+ RUN_TEST(set_host_behavior);
+ RUN_TEST(get_host_behavior);
+ RUN_TEST(set_sanitize);
+ RUN_TEST(get_sanitize);
+ RUN_TEST(set_endurance_evt_cfg);
+ RUN_TEST(get_endurance_event_cfg);
+ RUN_TEST(set_iocs_profile);
+ RUN_TEST(get_iocs_profile);
+ RUN_TEST(set_sw_progress);
+ RUN_TEST(get_sw_progress);
+ RUN_TEST(set_host_id);
+ RUN_TEST(set_host_id_extended);
+ RUN_TEST(get_host_id);
+ RUN_TEST(get_host_id_extended);
+ RUN_TEST(set_resv_mask);
+ RUN_TEST(get_resv_mask);
+ RUN_TEST(set_resv_persist);
+ RUN_TEST(get_resv_persist);
+ RUN_TEST(set_write_protect);
+ RUN_TEST(get_write_protect);
+ RUN_TEST(set_status_code_error);
+ RUN_TEST(set_kernel_error);
+ RUN_TEST(get_status_code_error);
+ RUN_TEST(get_kernel_error);
+}
diff --git a/test/ioctl/identify.c b/test/ioctl/identify.c
new file mode 100644
index 0000000..ccde02b
--- /dev/null
+++ b/test/ioctl/identify.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include <libnvme.h>
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mock.h"
+#include "util.h"
+
+#define TEST_FD 0xFD
+#define TEST_NSID 0x12345678
+#define TEST_NVMSETID 0xABCD
+#define TEST_UUID 123
+#define TEST_CSI NVME_CSI_KV
+#define TEST_CNTID 0x4321
+#define TEST_DOMID 0xFEDC
+#define TEST_ENDGID 0x0123
+#define TEST_SC NVME_SC_INVALID_FIELD
+
+static void test_ns(void)
+{
+ struct nvme_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ctrl(void)
+{
+ struct nvme_id_ctrl expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CTRL,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_active_ns_list(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_ACTIVE_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_active_ns_list(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_descs(void)
+{
+ uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE];
+ struct nvme_ns_id_desc *id;
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_DESC_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(expected_id, sizeof(expected_id));
+ id = calloc(1, NVME_IDENTIFY_DATA_SIZE);
+ check(id, "memory allocation failed");
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_descs(TEST_FD, TEST_NSID, id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(id, expected_id, sizeof(expected_id), "incorrect identify data");
+ free(id);
+}
+
+static void test_nvmset_list(void)
+{
+ struct nvme_id_nvmset_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST,
+ .cdw11 = TEST_NVMSETID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_csi(void)
+{
+ uint8_t expected_id[NVME_IDENTIFY_DATA_SIZE];
+ uint8_t id[NVME_IDENTIFY_DATA_SIZE] = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS,
+ .cdw11 = TEST_CSI << 24,
+ .cdw14 = TEST_UUID,
+ .out_data = expected_id,
+ };
+ int err;
+
+ arbitrary(expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_csi(TEST_FD, TEST_NSID, TEST_UUID, TEST_CSI, id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(id, expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_zns_identify_ns(void)
+{
+ struct nvme_zns_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS,
+ .cdw11 = NVME_CSI_ZNS << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_zns_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_nvm_identify_ctrl(void)
+{
+ struct nvme_id_ctrl_nvm expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL,
+ .cdw11 = NVME_CSI_NVM << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_nvm_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_zns_identify_ctrl(void)
+{
+ struct nvme_zns_id_ctrl expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_CTRL,
+ .cdw11 = NVME_CSI_ZNS << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_zns_identify_ctrl(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_active_ns_list_csi(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_NS_ACTIVE_LIST,
+ .cdw11 = TEST_CSI << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_active_ns_list_csi(
+ TEST_FD, TEST_NSID, TEST_CSI, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_independent_identify_ns(void)
+{
+ struct nvme_id_independent_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ /* That's a mouthful! */
+ err = nvme_identify_independent_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns_list(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns_list(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns(void)
+{
+ struct nvme_id_ns expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ALLOCATED_NS,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_nsid_ctrl_list(void)
+{
+ struct nvme_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_NS_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nsid_ctrl_list(TEST_FD, TEST_NSID, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ctrl_list(void)
+{
+ struct nvme_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ctrl_list(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_primary_ctrl(void)
+{
+ struct nvme_primary_ctrl_cap expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_PRIMARY_CTRL_CAP,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_primary_ctrl(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_secondary_ctrl_list(void)
+{
+ struct nvme_secondary_ctrl_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_secondary_ctrl_list(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_ns_granularity(void)
+{
+ struct nvme_id_ns_granularity_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS_GRANULARITY,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns_granularity(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_uuid(void)
+{
+ struct nvme_id_uuid_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_UUID_LIST,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_uuid(TEST_FD, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_domain_list(void)
+{
+ struct nvme_id_domain_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_DOMAIN_LIST,
+ .cdw11 = TEST_DOMID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_domain_list(TEST_FD, TEST_DOMID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_endurance_group_list(void)
+{
+ struct nvme_id_endurance_group_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_ENDURANCE_GROUP_ID,
+ .cdw11 = TEST_ENDGID,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_endurance_group_list(TEST_FD, TEST_ENDGID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_allocated_ns_list_csi(void)
+{
+ struct nvme_ns_list expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(expected_id),
+ .cdw10 = NVME_IDENTIFY_CNS_CSI_ALLOCATED_NS_LIST,
+ .cdw11 = TEST_CSI << 24,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_allocated_ns_list_csi(
+ TEST_FD, TEST_NSID, TEST_CSI, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+static void test_iocs(void)
+{
+ struct nvme_id_iocs expected_id, id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(expected_id),
+ .cdw10 = TEST_CNTID << 16
+ | NVME_IDENTIFY_CNS_COMMAND_SET_STRUCTURE,
+ .out_data = &expected_id,
+ };
+ int err;
+
+ arbitrary(&expected_id, sizeof(expected_id));
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_iocs(TEST_FD, TEST_CNTID, &id);
+ end_mock_cmds();
+ check(err == 0, "identify returned error %d, errno %m", err);
+ cmp(&id, &expected_id, sizeof(id), "incorrect identify data");
+}
+
+/*
+ * All identify functions tail-call nvme_identify(),
+ * so testing errors in any of them will do
+ */
+
+static void test_status_code_error(void)
+{
+ struct nvme_id_nvmset_list id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .data_len = sizeof(id),
+ .cdw10 = NVME_IDENTIFY_CNS_NVMSET_LIST,
+ .cdw11 = TEST_NVMSETID,
+ .err = TEST_SC,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_nvmset_list(TEST_FD, TEST_NVMSETID, &id);
+ end_mock_cmds();
+ check(err == TEST_SC, "got error %d, expected %d", err, TEST_SC);
+}
+
+static void test_kernel_error(void)
+{
+ struct nvme_id_ns id = {};
+ struct mock_cmd mock_admin_cmd = {
+ .opcode = nvme_admin_identify,
+ .nsid = TEST_NSID,
+ .data_len = sizeof(id),
+ .cdw10 = NVME_IDENTIFY_CNS_NS,
+ .err = -EIO,
+ };
+ int err;
+
+ set_mock_admin_cmds(&mock_admin_cmd, 1);
+ err = nvme_identify_ns(TEST_FD, TEST_NSID, &id);
+ end_mock_cmds();
+ check(err == -1, "got error %d, expected -1", err);
+ check(errno == EIO, "unexpected error %m");
+}
+
+static void run_test(const char *test_name, void (*test_fn)(void))
+{
+ printf("Running test %s...", test_name);
+ fflush(stdout);
+ test_fn();
+ puts(" OK");
+}
+
+#define RUN_TEST(name) run_test(#name, test_ ## name)
+
+int main(void)
+{
+ set_mock_fd(TEST_FD);
+ RUN_TEST(ns);
+ RUN_TEST(ctrl);
+ RUN_TEST(active_ns_list);
+ RUN_TEST(ns_descs);
+ RUN_TEST(nvmset_list);
+ RUN_TEST(ns_csi);
+ RUN_TEST(zns_identify_ns);
+ RUN_TEST(nvm_identify_ctrl);
+ RUN_TEST(zns_identify_ctrl);
+ RUN_TEST(active_ns_list_csi);
+ RUN_TEST(independent_identify_ns);
+ RUN_TEST(allocated_ns_list);
+ RUN_TEST(allocated_ns);
+ RUN_TEST(nsid_ctrl_list);
+ RUN_TEST(ctrl_list);
+ RUN_TEST(primary_ctrl);
+ RUN_TEST(secondary_ctrl_list);
+ RUN_TEST(ns_granularity);
+ RUN_TEST(uuid);
+ RUN_TEST(domain_list);
+ RUN_TEST(endurance_group_list);
+ RUN_TEST(allocated_ns_list_csi);
+ RUN_TEST(iocs);
+ RUN_TEST(status_code_error);
+ RUN_TEST(kernel_error);
+}
diff --git a/test/ioctl/meson.build b/test/ioctl/meson.build
new file mode 100644
index 0000000..b329d27
--- /dev/null
+++ b/test/ioctl/meson.build
@@ -0,0 +1,42 @@
+mock_ioctl = library(
+ 'mock-ioctl',
+ ['mock.c', 'util.c'],
+)
+
+# Add mock-ioctl to the LD_PRELOAD path so it overrides libc.
+# Append to LD_PRELOAD so existing libraries, e.g. libasan, are kept.
+# If libasan isn't specified in the LD_PRELOAD path, ASAN warns about mock-ioctl
+# being loaded first because its memory allocations might not get intercepted.
+# But it appears this isn't a problem; ASAN errors in mock-ioctl are reported.
+# This is likely because the executable still links with libasan before libc.
+mock_ioctl_env = environment()
+mock_ioctl_env.append('LD_PRELOAD', mock_ioctl.full_path())
+mock_ioctl_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0')
+
+discovery = executable(
+ 'test-discovery',
+ 'discovery.c',
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir],
+ link_with: mock_ioctl,
+)
+
+test('discovery', discovery, env: mock_ioctl_env)
+
+features = executable(
+ 'test-features',
+ 'features.c',
+ dependencies: libnvme_dep,
+ link_with: mock_ioctl,
+)
+
+test('features', features, env: mock_ioctl_env)
+
+identify = executable(
+ 'test-identify',
+ 'identify.c',
+ dependencies: libnvme_dep,
+ link_with: mock_ioctl,
+)
+
+test('identify', identify, env: mock_ioctl_env)
diff --git a/test/ioctl/mock.c b/test/ioctl/mock.c
new file mode 100644
index 0000000..a97a357
--- /dev/null
+++ b/test/ioctl/mock.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "mock.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "../../src/nvme/ioctl.h"
+#include "util.h"
+
+struct mock_cmds {
+ const char *name;
+ const struct mock_cmd *cmds;
+ size_t remaining_cmds;
+};
+
+static int mock_fd = -1;
+static struct mock_cmds mock_admin_cmds = {.name = "admin"};
+static struct mock_cmds mock_io_cmds = {.name = "IO"};
+
+static void set_mock_cmds(
+ struct mock_cmds *mock_cmds, const struct mock_cmd *cmds, size_t len)
+{
+ mock_cmds->cmds = cmds;
+ mock_cmds->remaining_cmds = len;
+}
+
+static void mock_cmds_done(const struct mock_cmds *mock_cmds)
+{
+ check(!mock_cmds->remaining_cmds,
+ "%zu %s commands not executed",
+ mock_cmds->remaining_cmds, mock_cmds->name);
+}
+
+void set_mock_fd(int fd)
+{
+ mock_fd = fd;
+}
+
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_admin_cmds, cmds, len);
+}
+
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_io_cmds, cmds, len);
+}
+
+void end_mock_cmds(void)
+{
+ mock_cmds_done(&mock_admin_cmds);
+ mock_cmds_done(&mock_io_cmds);
+}
+
+#define execute_ioctl(cmd, mock_cmd) ({ \
+ check((cmd)->opcode == (mock_cmd)->opcode, \
+ "got opcode %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->opcode, (mock_cmd)->opcode); \
+ check((cmd)->flags == (mock_cmd)->flags, \
+ "got flags %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->flags, (mock_cmd)->flags); \
+ check((cmd)->nsid == (mock_cmd)->nsid, \
+ "got nsid %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->nsid, (mock_cmd)->nsid); \
+ check((cmd)->cdw2 == (mock_cmd)->cdw2, \
+ "got cdw2 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw2, (mock_cmd)->cdw2); \
+ check((cmd)->cdw3 == (mock_cmd)->cdw3, \
+ "got cdw3 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw3, (mock_cmd)->cdw3); \
+ check((cmd)->metadata_len == (mock_cmd)->metadata_len, \
+ "got metadata_len %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->metadata_len, (mock_cmd)->metadata_len); \
+ if ((cmd)->metadata_len) { \
+ cmp((void const *)(uintptr_t)(cmd)->metadata, \
+ (mock_cmd)->metadata, \
+ (cmd)->metadata_len, \
+ "incorrect metadata"); \
+ } \
+ __u32 data_len = (cmd)->data_len; \
+ check(data_len == (mock_cmd)->data_len, \
+ "got data_len %" PRIu32 ", expected %" PRIu32, \
+ data_len, (mock_cmd)->data_len); \
+ void *data = (void *)(uintptr_t)(cmd)->addr; \
+ if ((mock_cmd)->in_data) { \
+ cmp(data, (mock_cmd)->in_data, data_len, "incorrect data"); \
+ } \
+ check((cmd)->cdw10 == (mock_cmd)->cdw10, \
+ "got cdw10 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw10, (mock_cmd)->cdw10); \
+ check((cmd)->cdw11 == (mock_cmd)->cdw11, \
+ "got cdw11 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw11, (mock_cmd)->cdw11); \
+ check((cmd)->cdw12 == (mock_cmd)->cdw12, \
+ "got cdw12 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw12, (mock_cmd)->cdw12); \
+ check((cmd)->cdw13 == (mock_cmd)->cdw13, \
+ "got cdw13 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw13, (mock_cmd)->cdw13); \
+ check((cmd)->cdw14 == (mock_cmd)->cdw14, \
+ "got cdw14 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw14, (mock_cmd)->cdw14); \
+ check((cmd)->cdw15 == (mock_cmd)->cdw15, \
+ "got cdw15 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw15, (mock_cmd)->cdw15); \
+ check((cmd)->timeout_ms == (mock_cmd)->timeout_ms, \
+ "got timeout_ms %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->timeout_ms, (mock_cmd)->timeout_ms); \
+ (cmd)->result = (mock_cmd)->result; \
+ if ((mock_cmd)->out_data) { \
+ memcpy(data, (mock_cmd)->out_data, data_len); \
+ } \
+})
+
+#ifdef HAVE_GLIBC_IOCTL
+int ioctl(int fd, unsigned long request, ...)
+#else
+int ioctl(int fd, int request, ...)
+#endif
+{
+ struct mock_cmds *mock_cmds;
+ bool result64;
+ const struct mock_cmd *mock_cmd;
+ va_list args;
+ void *cmd;
+
+ check(fd == mock_fd, "got fd %d, expected %d", fd, mock_fd);
+ switch (request) {
+ case NVME_IOCTL_ADMIN_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_ADMIN64_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = true;
+ break;
+ case NVME_IOCTL_IO_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_IO64_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = true;
+ break;
+ default:
+ fail("unexpected %s %lu", __func__, (unsigned long) request);
+ }
+ check(mock_cmds->remaining_cmds,
+ "unexpected %s command", mock_cmds->name);
+ mock_cmd = mock_cmds->cmds++;
+ mock_cmds->remaining_cmds--;
+
+ va_start(args, request);
+ cmd = va_arg(args, void *);
+ va_end(args);
+ if (result64) {
+ execute_ioctl((struct nvme_passthru_cmd64 *)cmd, mock_cmd);
+ } else {
+ check((uint32_t)mock_cmd->result == mock_cmd->result,
+ "expected 64-bit %s for result %" PRIu64,
+ __func__, mock_cmd->result);
+ execute_ioctl((struct nvme_passthru_cmd *)cmd, mock_cmd);
+ }
+ if (mock_cmd->err < 0) {
+ errno = -mock_cmd->err;
+ return -1;
+ }
+
+ return mock_cmd->err;
+}
diff --git a/test/ioctl/mock.h b/test/ioctl/mock.h
new file mode 100644
index 0000000..192eba8
--- /dev/null
+++ b/test/ioctl/mock.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _LIBNVME_TEST_IOCTL_MOCK_H
+#define _LIBNVME_TEST_IOCTL_MOCK_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * struct mock_cmd - a mock NVMe passthru ioctl() invocation
+ * @opcode: the expected `opcode` passed to ioctl()
+ * @flags: the expected `flags` passed to ioctl()
+ * @nsid: the expected `nsid` passed to ioctl()
+ * @cdw2: the expected `cdw2` passed to ioctl()
+ * @cdw3: the expected `cdw3` passed to ioctl()
+ * @metadata: the expected `metadata` of length `metadata_len` passed to ioctl()
+ * @in_data: the expected `addr` of length `data_len` passed to ioctl().
+ * Set this to NULL to skip checking the data,
+ * for example if the command is in the read direction.
+ * @metadata_len: the expected `metadata_len` passed to ioctl()
+ * @data_len: the expected `data_len` passed to ioctl()
+ * @cdw10: the expected `cdw10` passed to ioctl()
+ * @cdw11: the expected `cdw11` passed to ioctl()
+ * @cdw12: the expected `cdw12` passed to ioctl()
+ * @cdw13: the expected `cdw13` passed to ioctl()
+ * @cdw14: the expected `cdw14` passed to ioctl()
+ * @cdw15: the expected `cdw15` passed to ioctl()
+ * @timeout_ms: the expected `timeout_ms` passed to ioctl()
+ * @out_data: if not NULL, `data_len` bytes to copy to the caller's `addr`
+ * @result: copied to the caller's `result`.
+ * If `result` doesn't fit in a u32, the ioctl() must be the 64-bit one.
+ * @err: If negative, ioctl() returns -1 and sets `errno` to `-err`.
+ * Otherwise, ioctl() returns `err`, representing a NVMe status code.
+ */
+struct mock_cmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint32_t nsid;
+ uint32_t cdw2;
+ uint32_t cdw3;
+ const void *metadata;
+ const void *in_data;
+ uint32_t metadata_len;
+ uint32_t data_len;
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+ uint32_t timeout_ms;
+ const void *out_data;
+ uint64_t result;
+ int err;
+};
+
+/**
+ * set_mock_fd() - sets the expected file descriptor for NVMe passthru ioctls()
+ * @fd: file descriptor expected to be passed to ioctl()
+ */
+void set_mock_fd(int fd);
+
+/**
+ * set_mock_admin_cmds() - mocks NVMe admin passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe admin passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_io_cmds(), but for admin commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * set_mock_io_cmds() - mocks NVMe IO passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe IO passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_admin_cmds(), but for IO commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * end_mock_cmds() - finishes mocking NVMe passthru ioctl() invocations
+ *
+ * Checks that all mock ioctl() invocations were performed.
+ */
+void end_mock_cmds(void);
+
+#endif /* #ifndef _LIBNVME_TEST_IOCTL_MOCK_H */
diff --git a/test/ioctl/util.c b/test/ioctl/util.c
new file mode 100644
index 0000000..09c6e7f
--- /dev/null
+++ b/test/ioctl/util.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "util.h"
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void hexdump(const uint8_t *buf, size_t len)
+{
+ size_t i = 0;
+
+ if (!len)
+ return;
+
+ for (;;) {
+ fprintf(stderr, "%02X", buf[i++]);
+ if (i >= len)
+ break;
+
+ fputc(i % 16 > 0 ? ' ' : '\n', stderr);
+ }
+ fputc('\n', stderr);
+}
+
+void fail(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fputc('\n', stderr);
+ abort();
+}
+
+void cmp(const void *actual, const void *expected, size_t len, const char *msg)
+{
+ if (memcmp(actual, expected, len) == 0)
+ return;
+
+ fputs(msg, stderr);
+ fputs("\nactual:\n", stderr);
+ hexdump(actual, len);
+ fputs("expected:\n", stderr);
+ hexdump(expected, len);
+ abort();
+}
+
+void arbitrary(void *buf_, size_t len)
+{
+ uint8_t *buf = buf_;
+
+ while (len--)
+ *(buf++) = rand();
+}
+
+size_t arbitrary_range(size_t max)
+{
+ size_t value;
+ arbitrary(&value, sizeof(value));
+ return value % max;
+}
diff --git a/test/ioctl/util.h b/test/ioctl/util.h
new file mode 100644
index 0000000..aa86422
--- /dev/null
+++ b/test/ioctl/util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _LIBNVME_TEST_IOCTL_UTIL_H
+#define _LIBNVME_TEST_IOCTL_UTIL_H
+
+#include <stddef.h>
+#include <stdnoreturn.h>
+
+noreturn void fail(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+#define check(condition, fmt...) ((condition) || (fail(fmt), 0))
+
+void cmp(const void *actual, const void *expected, size_t len, const char *msg);
+
+void arbitrary(void *buf, size_t len);
+
+size_t arbitrary_range(size_t max);
+
+#endif /* #ifndef _LIBNVME_TEST_IOCTL_UTIL_H */
diff --git a/test/meson.build b/test/meson.build
new file mode 100644
index 0000000..93e6999
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1,103 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of libnvme.
+# Copyright (c) 2021 Dell Inc.
+#
+# Authors: Martin Belanger <Martin.Belanger@dell.com>
+
+# These tests all require interaction with a real NVMe device, so we don't
+# define as meson unit-tests, and therefore get run as part of the 'test'
+# target. However, they're available for developer use, when hardware is
+# available.
+main = executable(
+ 'main-test',
+ ['test.c'],
+ dependencies: [libnvme_dep],
+ include_directories: [incdir, internal_incdir]
+)
+
+if cxx_available
+ cpp = executable(
+ 'test-cpp',
+ ['cpp.cc'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+ )
+endif
+
+register = executable(
+ 'test-register',
+ ['register.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+zns = executable(
+ 'test-zns',
+ ['zns.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+mi = executable(
+ 'test-mi',
+ ['mi.c', 'utils.c'],
+ dependencies: libnvme_mi_test_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+test('mi', mi)
+
+mi_mctp = executable(
+ 'test-mi-mctp',
+ ['mi-mctp.c', 'utils.c'],
+ dependencies: libnvme_mi_test_dep,
+ include_directories: [incdir, internal_incdir],
+)
+
+test('mi-mctp', mi_mctp)
+
+uuid = executable(
+ 'test-uuid',
+ ['uuid.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+test('uuid', uuid)
+
+if conf.get('HAVE_NETDB')
+ mock_ifaddrs = library(
+ 'mock-ifaddrs',
+ ['mock-ifaddrs.c', ],
+ )
+
+ # See comment in test/ioctl/meson.build explaining how LD_PRELOAD is used
+ mock_ifaddrs_env = environment()
+ mock_ifaddrs_env.append('LD_PRELOAD', mock_ifaddrs.full_path())
+ mock_ifaddrs_env.set('ASAN_OPTIONS', 'verify_asan_link_order=0')
+
+ tree = executable(
+ 'tree',
+ ['tree.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir],
+ link_with: mock_ifaddrs,
+ )
+
+ test('tree', tree, env: mock_ifaddrs_env)
+
+ test_util = executable(
+ 'test-util',
+ ['test-util.c'],
+ include_directories: [incdir, internal_incdir]
+ )
+ test('util', test_util)
+endif
+
+subdir('ioctl')
+subdir('nbft')
+
+if json_c_dep.found()
+ subdir('sysfs')
+endif
diff --git a/test/mi-mctp.c b/test/mi-mctp.c
new file mode 100644
index 0000000..5711c03
--- /dev/null
+++ b/test/mi-mctp.c
@@ -0,0 +1,780 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2022 Code Construct
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+#include "libnvme-mi.h"
+#include "nvme/private.h"
+#include "utils.h"
+
+/* 4096 byte max MCTP message, plus space for header data */
+#define MAX_BUFSIZ 8192
+
+struct test_peer;
+
+typedef int (*rx_test_fn)(struct test_peer *peer, void *buf, size_t len);
+typedef int (*poll_test_fn)(struct test_peer *peer,
+ struct pollfd *fds, nfds_t nfds, int timeout);
+
+/* Our fake MCTP "peer".
+ *
+ * The terms TX (transmit) and RX (receive) are from the perspective of
+ * the NVMe device. TX is device-to-libnvme, RX is libnvme-to-device.
+ *
+ * The RX and TX buffers are linear versions of the data sent and received by
+ * libnvme-mi, and *include* the MCTP message type byte (even though it's
+ * omitted in the sendmsg/recvmsg interface), so that the buffer inspection
+ * in the tests can exactly match the NVMe-MI spec packet diagrams.
+ */
+static struct test_peer {
+ /* rx (sendmsg) data sent from libnvme, and return value */
+ unsigned char rx_buf[MAX_BUFSIZ];
+ size_t rx_buf_len;
+ ssize_t rx_rc; /* if zero, return the sendmsg len */
+ int rx_errno;
+
+ /* tx (recvmsg) data to be received by libnvme and return value */
+ unsigned char tx_buf[MAX_BUFSIZ];
+ size_t tx_buf_len;
+ ssize_t tx_rc; /* if zero, return the recvmsg len */
+ int tx_errno;
+
+ /* Optional, called before TX, may set tx_buf according to request.
+ * Return value stored in tx_res, may be used by test */
+ rx_test_fn tx_fn;
+ void *tx_data;
+ int tx_fn_res;
+
+ poll_test_fn poll_fn;
+ void *poll_data;
+
+ /* store sd from socket() setup */
+ int sd;
+} test_peer;
+
+/* ensure tests start from a standard state */
+void reset_test_peer(void)
+{
+ int tmp = test_peer.sd;
+ memset(&test_peer, 0, sizeof(test_peer));
+ test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
+ test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
+ test_peer.sd = tmp;
+}
+
+/* calculate MIC of peer-to-libnvme data, expand buf by 4 bytes and insert
+ * the new MIC */
+static void test_set_tx_mic(struct test_peer *peer)
+{
+ extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
+ __u32 crc = 0xffffffff;
+ __le32 crc_le;
+
+ assert(peer->tx_buf_len + sizeof(crc_le) <= MAX_BUFSIZ);
+
+ crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len);
+ crc_le = cpu_to_le32(~crc);
+ memcpy(peer->tx_buf + peer->tx_buf_len, &crc_le, sizeof(crc_le));
+ peer->tx_buf_len += sizeof(crc_le);
+}
+
+int __wrap_socket(int family, int type, int protocol)
+{
+ /* we do an open here to give the mi-mctp code something to close() */
+ test_peer.sd = open("/dev/null", 0);
+ return test_peer.sd;
+}
+
+ssize_t __wrap_sendmsg(int sd, const struct msghdr *hdr, int flags)
+{
+ size_t i, pos;
+
+ assert(sd == test_peer.sd);
+
+ test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
+
+ /* gather iovec into buf */
+ for (i = 0, pos = 1; i < hdr->msg_iovlen; i++) {
+ struct iovec *iov = &hdr->msg_iov[i];
+
+ assert(pos + iov->iov_len < MAX_BUFSIZ - 1);
+ memcpy(test_peer.rx_buf + pos, iov->iov_base, iov->iov_len);
+ pos += iov->iov_len;
+ }
+
+ test_peer.rx_buf_len = pos;
+
+ errno = test_peer.rx_errno;
+
+ return test_peer.rx_rc ?: (pos - 1);
+}
+
+ssize_t __wrap_recvmsg(int sd, struct msghdr *hdr, int flags)
+{
+ size_t i, pos, len;
+
+ assert(sd == test_peer.sd);
+
+ if (test_peer.tx_fn) {
+ test_peer.tx_fn_res = test_peer.tx_fn(&test_peer,
+ test_peer.rx_buf,
+ test_peer.rx_buf_len);
+ } else {
+ /* set up a few default response fields; caller may have
+ * initialised the rest of the response */
+ test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
+ test_peer.tx_buf[1] = test_peer.rx_buf[1] | (NVME_MI_ROR_RSP << 7);
+ test_set_tx_mic(&test_peer);
+ }
+
+ /* scatter buf into iovec */
+ for (i = 0, pos = 1; i < hdr->msg_iovlen && pos < test_peer.tx_buf_len;
+ i++) {
+ struct iovec *iov = &hdr->msg_iov[i];
+
+ len = iov->iov_len;
+ if (len > test_peer.tx_buf_len - pos)
+ len = test_peer.tx_buf_len - pos;
+
+ memcpy(iov->iov_base, test_peer.tx_buf + pos, len);
+ pos += len;
+ }
+
+ errno = test_peer.tx_errno;
+
+ return test_peer.tx_rc ?: (pos - 1);
+}
+
+int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+ if (!test_peer.poll_fn)
+ return 1;
+
+ return test_peer.poll_fn(&test_peer, fds, nfds, timeout);
+}
+
+struct mctp_ioc_tag_ctl;
+
+#ifdef SIOCMCTPALLOCTAG
+int test_ioctl_tag(int sd, unsigned long req, struct mctp_ioc_tag_ctl *ctl)
+{
+ assert(sd == test_peer.sd);
+
+ switch (req) {
+ case SIOCMCTPALLOCTAG:
+ ctl->tag = 1 | MCTP_TAG_PREALLOC | MCTP_TAG_OWNER;
+ break;
+ case SIOCMCTPDROPTAG:
+ assert(tag == 1 | MCTP_TAG_PREALLOC | MCTP_TAG_OWNER);
+ break;
+ };
+
+ return 0;
+}
+#else
+int test_ioctl_tag(int sd, unsigned long req, struct mctp_ioc_tag_ctl *ctl)
+{
+ assert(sd == test_peer.sd);
+ return 0;
+}
+#endif
+
+static struct __mi_mctp_socket_ops ops = {
+ __wrap_socket,
+ __wrap_sendmsg,
+ __wrap_recvmsg,
+ __wrap_poll,
+ test_ioctl_tag,
+};
+
+/* tests */
+static void test_rx_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->rx_rc = -1;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static int tx_none(struct test_peer *peer, void *buf, size_t len)
+{
+ return 0;
+}
+
+static void test_tx_none(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->tx_buf_len = 0;
+ peer->tx_fn = tx_none;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static void test_tx_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->tx_rc = -1;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static void test_tx_short(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->tx_buf_len = 11;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static int poll_fn_err(struct test_peer *peer, struct pollfd *fds,
+ nfds_t nfds, int timeout)
+{
+ return -1;
+}
+
+static void test_poll_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->poll_fn = poll_fn_err;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static void test_read_mi_data(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ /* empty response data */
+ peer->tx_buf_len = 8 + 32;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+static void test_mi_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ /* simple error response */
+ peer->tx_buf[4] = 0x02; /* internal error */
+ peer->tx_buf_len = 8;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0x2);
+}
+
+static void setup_unaligned_ctrl_list_resp(struct test_peer *peer)
+{
+ /* even number of controllers */
+ peer->tx_buf[8] = 0x02;
+ peer->tx_buf[9] = 0x00;
+
+ /* controller ID 1 */
+ peer->tx_buf[10] = 0x01;
+ peer->tx_buf[11] = 0x00;
+
+ /* controller ID 2 */
+ peer->tx_buf[12] = 0x02;
+ peer->tx_buf[13] = 0x00;
+
+ peer->tx_buf_len = 14;
+}
+
+/* Will call through the xfer/submit API expecting a full-sized list (so
+ * resp->data_len is set to sizeof(list)), but the endpoint will return an
+ * unaligned short list.
+ */
+static void test_mi_resp_unaligned(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_ctrl_list list;
+ int rc;
+
+ setup_unaligned_ctrl_list_resp(peer);
+
+ memset(&list, 0, sizeof(list));
+
+ rc = nvme_mi_mi_read_mi_data_ctrl_list(ep, 0, &list);
+ assert(rc == 0);
+
+ assert(le16_to_cpu(list.num) == 2);
+ assert(le16_to_cpu(list.identifier[0]) == 1);
+ assert(le16_to_cpu(list.identifier[1]) == 2);
+}
+
+/* Will call through the xfer/submit API expecting an unaligned list,
+ * and get a response of exactly that size.
+ */
+static void test_mi_resp_unaligned_expected(nvme_mi_ep_t ep,
+ struct test_peer *peer)
+{
+ /* direct access to the raw submit() API */
+ extern int nvme_mi_submit(nvme_mi_ep_t ep, struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp);
+ struct nvme_mi_mi_resp_hdr resp_hdr;
+ struct nvme_mi_mi_req_hdr req_hdr;
+ struct nvme_ctrl_list list;
+ struct nvme_mi_resp resp;
+ struct nvme_mi_req req;
+ int rc;
+
+ setup_unaligned_ctrl_list_resp(peer);
+
+ memset(&list, 0, sizeof(list));
+
+ memset(&req_hdr, 0, sizeof(req_hdr));
+ req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME;
+ req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3);
+ req_hdr.opcode = nvme_mi_mi_opcode_mi_data_read;
+ req_hdr.cdw0 = cpu_to_le32(nvme_mi_dtyp_ctrl_list << 24);
+
+ memset(&req, 0, sizeof(req));
+ req.hdr = &req_hdr.hdr;
+ req.hdr_len = sizeof(req_hdr);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.hdr = &resp_hdr.hdr;
+ resp.hdr_len = sizeof(resp_hdr);
+ resp.data = &list;
+ resp.data_len = peer->tx_buf_len;
+
+ rc = nvme_mi_submit(ep, &req, &resp);
+ assert(rc == 0);
+ assert(resp.data_len == 6); /* 2-byte length, 2*2 byte controller IDs */
+
+ assert(le16_to_cpu(list.num) == 2);
+ assert(le16_to_cpu(list.identifier[0]) == 1);
+ assert(le16_to_cpu(list.identifier[1]) == 2);
+}
+
+static void test_admin_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+ assert(ctrl);
+
+ /* Simple error response, will be shorter than the expected Admin
+ * command response header. */
+ peer->tx_buf[4] = 0x02; /* internal error */
+ peer->tx_buf_len = 8;
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(nvme_status_get_type(rc) == NVME_STATUS_TYPE_MI);
+ assert(nvme_status_get_value(rc) == NVME_MI_RESP_INTERNAL_ERR);
+}
+
+/* test: all 4-byte aligned response sizes - should be decoded into the
+ * response status value. We use an admin command here as the header size will
+ * be larger than the minimum header size (it contains the completion
+ * doublewords), and we need to ensure that an error response is correctly
+ * interpreted, including having the MIC extracted from the message.
+ */
+static void test_admin_resp_sizes(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ unsigned int i;
+ int rc;
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+ assert(ctrl);
+
+ peer->tx_buf[4] = 0x02; /* internal error */
+
+ for (i = 8; i <= 4096 + 8; i+=4) {
+ peer->tx_buf_len = i;
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(nvme_status_get_type(rc) == NVME_STATUS_TYPE_MI);
+ assert(nvme_status_get_value(rc) == NVME_MI_RESP_INTERNAL_ERR);
+ }
+
+ nvme_mi_close_ctrl(ctrl);
+}
+
+/* test: timeout value passed to poll */
+static int poll_fn_timeout_value(struct test_peer *peer, struct pollfd *fds,
+ nfds_t nfds, int timeout)
+{
+ assert(timeout == 3141);
+ return 1;
+}
+
+static void test_poll_timeout_value(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ /* empty response data */
+ peer->tx_buf_len = 8 + 32;
+
+ peer->poll_fn = poll_fn_timeout_value;
+ nvme_mi_ep_set_timeout(ep, 3141);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+/* test: poll timeout expiry */
+static int poll_fn_timeout(struct test_peer *peer, struct pollfd *fds,
+ nfds_t nfds, int timeout)
+{
+ return 0;
+}
+
+static void test_poll_timeout(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ peer->poll_fn = poll_fn_timeout;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+ assert(errno == ETIMEDOUT);
+}
+
+/* test: send a More Processing Required response, then the actual response */
+struct mpr_tx_info {
+ int msg_no;
+ bool admin_quirk;
+ size_t final_len;
+};
+
+static int tx_mpr(struct test_peer *peer, void *buf, size_t len)
+{
+ struct mpr_tx_info *tx_info = peer->tx_data;
+
+ memset(peer->tx_buf, 0, sizeof(peer->tx_buf));
+ peer->tx_buf[0] = NVME_MI_MSGTYPE_NVME;
+ peer->tx_buf[1] = test_peer.rx_buf[1] | (NVME_MI_ROR_RSP << 7);
+
+ switch (tx_info->msg_no) {
+ case 1:
+ peer->tx_buf[4] = NVME_MI_RESP_MPR;
+ peer->tx_buf_len = 8;
+ if (tx_info->admin_quirk) {
+ peer->tx_buf_len = 20;
+ }
+ break;
+ case 2:
+ peer->tx_buf[4] = NVME_MI_RESP_SUCCESS;
+ peer->tx_buf_len = tx_info->final_len;
+ break;
+ default:
+ assert(0);
+ }
+
+ test_set_tx_mic(peer);
+
+ tx_info->msg_no++;
+
+ return 0;
+}
+
+static void test_mpr_mi(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ struct mpr_tx_info tx_info;
+ int rc;
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_mi_resp_hdr) + sizeof(ss_info);
+ tx_info.admin_quirk = false;
+
+ peer->tx_fn = tx_mpr;
+ peer->tx_data = &tx_info;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+static void test_mpr_admin(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct mpr_tx_info tx_info;
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_admin_resp_hdr) + sizeof(id);
+ tx_info.admin_quirk = false;
+
+ peer->tx_fn = tx_mpr;
+ peer->tx_data = &tx_info;
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(rc == 0);
+
+ nvme_mi_close_ctrl(ctrl);
+}
+
+/* We have seen drives that send a MPR response as a full Admin message,
+ * rather than a MI message; these have a larger message body
+ */
+static void test_mpr_admin_quirked(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct mpr_tx_info tx_info;
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_admin_resp_hdr) + sizeof(id);
+ tx_info.admin_quirk = true;
+
+ peer->tx_fn = tx_mpr;
+ peer->tx_data = &tx_info;
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(rc == 0);
+
+ nvme_mi_close_ctrl(ctrl);
+}
+
+/* helpers for the MPR + poll tests */
+struct mpr_poll_info {
+ int poll_no;
+ uint16_t mprt;
+ unsigned int timeouts[2];
+};
+
+static int poll_fn_mpr_poll(struct test_peer *peer, struct pollfd *fds,
+ nfds_t nfds, int timeout)
+{
+ struct mpr_poll_info *info = peer->poll_data;
+
+ switch (info->poll_no) {
+ case 1:
+ case 2:
+ assert(timeout == info->timeouts[info->poll_no - 1]);
+ break;
+ default:
+ assert(0);
+ }
+
+ info->poll_no++;
+ return 1;
+}
+
+static int tx_fn_mpr_poll(struct test_peer *peer, void *buf, size_t len)
+{
+ struct mpr_tx_info *tx_info = peer->tx_data;
+ struct mpr_poll_info *poll_info = peer->poll_data;
+ unsigned int mprt;
+
+ memset(peer->tx_buf, 0, sizeof(peer->tx_buf));
+ peer->tx_buf[0] = NVME_MI_MSGTYPE_NVME;
+ peer->tx_buf[1] = test_peer.rx_buf[1] | (NVME_MI_ROR_RSP << 7);
+
+ switch (tx_info->msg_no) {
+ case 1:
+ peer->tx_buf[4] = NVME_MI_RESP_MPR;
+ peer->tx_buf_len = 8;
+ mprt = poll_info->mprt;
+ peer->tx_buf[7] = mprt >> 8;
+ peer->tx_buf[6] = mprt & 0xff;
+ break;
+ case 2:
+ peer->tx_buf[4] = NVME_MI_RESP_SUCCESS;
+ peer->tx_buf_len = tx_info->final_len;
+ break;
+ default:
+ assert(0);
+ }
+
+ test_set_tx_mic(peer);
+
+ tx_info->msg_no++;
+
+ return 0;
+}
+
+/* test: correct timeout value used from MPR response */
+static void test_mpr_timeouts(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ struct mpr_poll_info poll_info;
+ struct mpr_tx_info tx_info;
+ int rc;
+
+ nvme_mi_ep_set_timeout(ep, 3141);
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_mi_resp_hdr) + sizeof(ss_info);
+
+ poll_info.poll_no = 1;
+ poll_info.mprt = 1234;
+ poll_info.timeouts[0] = 3141;
+ poll_info.timeouts[1] = 1234 * 100;
+
+ peer->tx_fn = tx_fn_mpr_poll;
+ peer->tx_data = &tx_info;
+
+ peer->poll_fn = poll_fn_mpr_poll;
+ peer->poll_data = &poll_info;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+/* test: MPR value is limited to the max mpr */
+static void test_mpr_timeout_clamp(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ struct mpr_poll_info poll_info;
+ struct mpr_tx_info tx_info;
+ int rc;
+
+ nvme_mi_ep_set_timeout(ep, 3141);
+ nvme_mi_ep_set_mprt_max(ep, 123400);
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_mi_resp_hdr) + sizeof(ss_info);
+
+ poll_info.poll_no = 1;
+ poll_info.mprt = 1235;
+ poll_info.timeouts[0] = 3141;
+ poll_info.timeouts[1] = 1234 * 100;
+
+ peer->tx_fn = tx_fn_mpr_poll;
+ peer->tx_data = &tx_info;
+
+ peer->poll_fn = poll_fn_mpr_poll;
+ peer->poll_data = &poll_info;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+/* test: MPR value of zero doesn't result in poll with zero timeout */
+static void test_mpr_mprt_zero(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ struct mpr_poll_info poll_info;
+ struct mpr_tx_info tx_info;
+ int rc;
+
+ nvme_mi_ep_set_timeout(ep, 3141);
+ nvme_mi_ep_set_mprt_max(ep, 123400);
+
+ tx_info.msg_no = 1;
+ tx_info.final_len = sizeof(struct nvme_mi_mi_resp_hdr) + sizeof(ss_info);
+
+ poll_info.poll_no = 1;
+ poll_info.mprt = 0;
+ poll_info.timeouts[0] = 3141;
+ poll_info.timeouts[1] = 3141;
+
+ peer->tx_fn = tx_fn_mpr_poll;
+ peer->tx_data = &tx_info;
+
+ peer->poll_fn = poll_fn_mpr_poll;
+ peer->poll_data = &poll_info;
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+#define DEFINE_TEST(name) { #name, test_ ## name }
+struct test {
+ const char *name;
+ void (*fn)(nvme_mi_ep_t, struct test_peer *);
+} tests[] = {
+ DEFINE_TEST(rx_err),
+ DEFINE_TEST(tx_none),
+ DEFINE_TEST(tx_err),
+ DEFINE_TEST(tx_short),
+ DEFINE_TEST(read_mi_data),
+ DEFINE_TEST(poll_err),
+ DEFINE_TEST(mi_resp_err),
+ DEFINE_TEST(mi_resp_unaligned),
+ DEFINE_TEST(mi_resp_unaligned_expected),
+ DEFINE_TEST(admin_resp_err),
+ DEFINE_TEST(admin_resp_sizes),
+ DEFINE_TEST(poll_timeout_value),
+ DEFINE_TEST(poll_timeout),
+ DEFINE_TEST(mpr_mi),
+ DEFINE_TEST(mpr_admin),
+ DEFINE_TEST(mpr_admin_quirked),
+ DEFINE_TEST(mpr_timeouts),
+ DEFINE_TEST(mpr_timeout_clamp),
+ DEFINE_TEST(mpr_mprt_zero),
+};
+
+static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep,
+ struct test_peer *peer)
+{
+ printf("Running test %s...", test->name);
+ fflush(stdout);
+ test->fn(ep, peer);
+ printf(" OK\n");
+ test_print_log_buf(logfd);
+}
+
+int main(void)
+{
+ nvme_root_t root;
+ nvme_mi_ep_t ep;
+ unsigned int i;
+ FILE *fd;
+
+ fd = test_setup_log();
+
+ __nvme_mi_mctp_set_ops(&ops);
+
+ root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
+ assert(root);
+
+ ep = nvme_mi_open_mctp(root, 0, 0);
+ assert(ep);
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ reset_test_peer();
+ run_test(&tests[i], fd, ep, &test_peer);
+ }
+
+ nvme_mi_close(ep);
+ nvme_mi_free_root(root);
+
+ test_close_log(fd);
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/mi.c b/test/mi.c
new file mode 100644
index 0000000..a86ba15
--- /dev/null
+++ b/test/mi.c
@@ -0,0 +1,1937 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2022 Code Construct
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+/* we define a custom transport, so need the internal headers */
+#include "nvme/private.h"
+
+#include "libnvme-mi.h"
+
+#include "utils.h"
+
+typedef int (*test_submit_cb)(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data);
+
+struct test_transport_data {
+ unsigned int magic;
+ bool named;
+ test_submit_cb submit_cb;
+ void *submit_cb_data;
+};
+
+static const int test_transport_magic = 0x74657374;
+
+static int test_transport_submit(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp)
+{
+ struct test_transport_data *tpd = ep->transport_data;
+
+ assert(tpd->magic == test_transport_magic);
+
+ /* start from a minimal response: zeroed data, nmp to match request */
+ memset(resp->hdr, 0, resp->hdr_len);
+ if (resp->data_len)
+ memset(resp->data, 0, resp->data_len);
+ resp->hdr->type = NVME_MI_MSGTYPE_NVME;
+ resp->hdr->nmp = req->hdr->nmp | (NVME_MI_ROR_RSP << 7);
+
+ if (tpd->submit_cb)
+ return tpd->submit_cb(ep, req, resp, tpd->submit_cb_data);
+
+ return 0;
+}
+
+static void test_transport_close(struct nvme_mi_ep *ep)
+{
+ struct test_transport_data *tpd = ep->transport_data;
+ assert(tpd->magic == test_transport_magic);
+ free(tpd);
+}
+
+static int test_transport_desc_ep(struct nvme_mi_ep *ep,
+ char *buf, size_t len)
+{
+ struct test_transport_data *tpd = ep->transport_data;
+
+ assert(tpd->magic == test_transport_magic);
+
+ if (!tpd->named)
+ return -1;
+
+ snprintf(buf, len, "test endpoint 0x%x", tpd->magic);
+
+ return 0;
+}
+
+/* internal test helper to generate correct response crc */
+static void test_transport_resp_calc_mic(struct nvme_mi_resp *resp)
+{
+ extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
+ __u32 crc = 0xffffffff;
+
+ crc = nvme_mi_crc32_update(crc, resp->hdr, resp->hdr_len);
+ crc = nvme_mi_crc32_update(crc, resp->data, resp->data_len);
+
+ resp->mic = ~crc;
+}
+
+static const struct nvme_mi_transport test_transport = {
+ .name = "test-mi",
+ .mic_enabled = true,
+ .submit = test_transport_submit,
+ .close = test_transport_close,
+ .desc_ep = test_transport_desc_ep,
+};
+
+static void test_set_transport_callback(nvme_mi_ep_t ep, test_submit_cb cb,
+ void *data)
+{
+ struct test_transport_data *tpd = ep->transport_data;
+ assert(tpd->magic == test_transport_magic);
+
+ tpd->submit_cb = cb;
+ tpd->submit_cb_data = data;
+}
+
+nvme_mi_ep_t nvme_mi_open_test(nvme_root_t root)
+{
+ struct test_transport_data *tpd;
+ struct nvme_mi_ep *ep;
+
+ ep = nvme_mi_init_ep(root);
+ assert(ep);
+
+ tpd = malloc(sizeof(*tpd));
+ assert(tpd);
+
+ tpd->magic = test_transport_magic;
+ tpd->named = true;
+
+ ep->transport = &test_transport;
+ ep->transport_data = tpd;
+
+ return ep;
+}
+
+unsigned int count_root_eps(nvme_root_t root)
+{
+ unsigned int i = 0;
+ nvme_mi_ep_t ep;
+
+ nvme_mi_for_each_endpoint(root, ep)
+ i++;
+
+ return i;
+}
+
+/* test that the root->endpoints list is updated on endpoint
+ * creation/destruction */
+static void test_endpoint_lifetime(nvme_mi_ep_t ep)
+{
+ nvme_root_t root = ep->root;
+ unsigned int count;
+ nvme_mi_ep_t ep2;
+
+ count = count_root_eps(root);
+ assert(count == 1);
+
+ ep2 = nvme_mi_open_test(root);
+ count = count_root_eps(root);
+ assert(count == 2);
+
+ nvme_mi_close(ep2);
+ count = count_root_eps(root);
+ assert(count == 1);
+}
+
+unsigned int count_ep_controllers(nvme_mi_ep_t ep)
+{
+ unsigned int i = 0;
+ nvme_mi_ctrl_t ctrl;
+
+ nvme_mi_for_each_ctrl(ep, ctrl)
+ i++;
+
+ return i;
+}
+
+/* test that the ep->controllers list is updated on controller
+ * creation/destruction */
+static void test_ctrl_lifetime(nvme_mi_ep_t ep)
+{
+ nvme_mi_ctrl_t c1, c2;
+ int count;
+
+ ep->controllers_scanned = true;
+
+ count = count_ep_controllers(ep);
+ assert(count == 0);
+
+ c1 = nvme_mi_init_ctrl(ep, 1);
+ count = count_ep_controllers(ep);
+ assert(count == 1);
+
+ c2 = nvme_mi_init_ctrl(ep, 2);
+ count = count_ep_controllers(ep);
+ assert(count == 2);
+
+ nvme_mi_close_ctrl(c1);
+ count = count_ep_controllers(ep);
+ assert(count == 1);
+
+ nvme_mi_close_ctrl(c2);
+ count = count_ep_controllers(ep);
+ assert(count == 0);
+}
+
+
+/* test: basic read MI datastructure command */
+static int test_read_mi_data_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 ror, mt, *hdr, *buf;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_MI);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
+
+ /* inspect response as raw bytes */
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_mi_mi_opcode_mi_data_read);
+
+ /* create basic response */
+ assert(resp->hdr_len >= sizeof(struct nvme_mi_mi_resp_hdr));
+ assert(resp->data_len >= 4);
+
+ hdr = (__u8 *)resp->hdr;
+ hdr[4] = 0; /* status */
+
+ buf = (__u8 *)resp->data;
+ memset(buf, 0, resp->data_len);
+ buf[0] = 1; /* NUMP */
+ buf[1] = 1; /* MJR */
+ buf[2] = 2; /* MNR */
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_read_mi_data(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_read_mi_data_cb, NULL);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc == 0);
+}
+
+/* test: failed transport */
+static int test_transport_fail_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ return -1;
+}
+
+static void test_transport_fail(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_transport_fail_cb, NULL);
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+static void test_transport_describe(nvme_mi_ep_t ep)
+{
+ struct test_transport_data *tpd;
+ char *str;
+
+ tpd = (struct test_transport_data *)ep->transport_data;
+
+ tpd->named = false;
+ str = nvme_mi_endpoint_desc(ep);
+ assert(str);
+ assert(!strcmp(str, "test-mi endpoint"));
+ free(str);
+
+ tpd->named = true;
+ str = nvme_mi_endpoint_desc(ep);
+ assert(str);
+ assert(!strcmp(str, "test-mi: test endpoint 0x74657374"));
+ free(str);
+}
+
+/* test: invalid crc */
+static int test_invalid_crc_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ resp->mic = 0;
+ return 0;
+}
+
+static void test_invalid_crc(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_invalid_crc_cb, NULL);
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc < 0);
+}
+
+/* test: test that the controller list populates the endpoint's list of
+ * controllers */
+static int test_scan_ctrl_list_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 ror, mt, *hdr, *buf;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_MI);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
+
+ /* inspect response as raw bytes */
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_mi_mi_opcode_mi_data_read);
+ assert(hdr[11] == nvme_mi_dtyp_ctrl_list);
+
+ /* create basic response */
+ assert(resp->hdr_len >= sizeof(struct nvme_mi_mi_resp_hdr));
+ assert(resp->data_len >= 4);
+
+ hdr = (__u8 *)resp->hdr;
+ hdr[4] = 0; /* status */
+
+ buf = (__u8 *)resp->data;
+ memset(buf, 0, resp->data_len);
+ buf[0] = 3; buf[1] = 0; /* num controllers */
+ buf[2] = 1; buf[3] = 0; /* id 1 */
+ buf[4] = 4; buf[5] = 0; /* id 4 */
+ buf[6] = 5; buf[7] = 0; /* id 5 */
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_scan_ctrl_list(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_ctrl *ctrl;
+
+ ep->controllers_scanned = false;
+
+ test_set_transport_callback(ep, test_scan_ctrl_list_cb, NULL);
+
+ nvme_mi_scan_ep(ep, false);
+
+ ctrl = nvme_mi_first_ctrl(ep);
+ assert(ctrl);
+ assert(ctrl->id == 1);
+
+ ctrl = nvme_mi_next_ctrl(ep, ctrl);
+ assert(ctrl);
+ assert(ctrl->id == 4);
+
+ ctrl = nvme_mi_next_ctrl(ep, ctrl);
+ assert(ctrl);
+ assert(ctrl->id == 5);
+
+ ctrl = nvme_mi_next_ctrl(ep, ctrl);
+ assert(ctrl == NULL);
+}
+
+/* test: simple NVMe admin request/response */
+static int test_admin_id_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 ror, mt, *hdr;
+ __u32 dlen, cdw10;
+ __u16 ctrl_id;
+ __u8 flags;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_ADMIN);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
+
+ /* inspect response as raw bytes */
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+ flags = hdr[5];
+
+ ctrl_id = hdr[7] << 8 | hdr[6];
+ assert(ctrl_id == 0x5); /* controller id */
+
+ /* we requested a full id; if we've set the length flag,
+ * ensure the length matches */
+ dlen = hdr[35] << 24 | hdr[34] << 16 | hdr[33] << 8 | hdr[32];
+ if (flags & 0x1) {
+ assert(dlen == sizeof(struct nvme_id_ctrl));
+ }
+ assert(!(flags & 0x2));
+
+ /* CNS value of 1 in cdw10 field */
+ cdw10 = hdr[47] << 24 | hdr[46] << 16 | hdr[45] << 8 | hdr[44];
+ assert(cdw10 == 0x1);
+
+ /* create valid (but somewhat empty) response */
+ hdr = (__u8 *)resp->hdr;
+ hdr[4] = 0x00; /* status: success */
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_id(nvme_mi_ep_t ep)
+{
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_id_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(rc == 0);
+}
+
+/* test: simple NVMe error response, error reported in the MI header */
+static int test_admin_err_mi_resp_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 ror, mt, *hdr;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_ADMIN);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
+
+ /* inspect response as raw bytes */
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ /* we need at least 8 bytes for error information */
+ assert(resp->hdr_len >= 8);
+
+ /* create error response */
+ hdr = (__u8 *)resp->hdr;
+ hdr[4] = 0x02; /* status: internal error */
+ hdr[5] = 0;
+ hdr[6] = 0;
+ hdr[7] = 0;
+ resp->hdr_len = 8;
+ resp->data_len = 0;
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_err_mi_resp(nvme_mi_ep_t ep)
+{
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_err_mi_resp_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(rc != 0);
+ assert(nvme_status_get_type(rc) == NVME_STATUS_TYPE_MI);
+ assert(nvme_status_get_value(rc) == NVME_MI_RESP_INTERNAL_ERR);
+}
+
+/* test: NVMe Admin error, with the error reported in the Admin response */
+static int test_admin_err_nvme_resp_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 ror, mt, *hdr;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_ADMIN);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
+
+ /* inspect response as raw bytes */
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ /* we need at least 8 bytes for error information */
+ assert(resp->hdr_len >= sizeof(struct nvme_mi_admin_resp_hdr));
+
+ /* create error response */
+ hdr = (__u8 *)resp->hdr;
+ hdr[4] = 0; /* MI status: success */
+ hdr[5] = 0;
+ hdr[6] = 0;
+ hdr[7] = 0;
+
+ hdr[16] = 0; /* cdw3: SC: internal, SCT: generic, DNR */
+ hdr[17] = 0;
+ hdr[18] = 0x0c;
+ hdr[19] = 0x80;
+
+ resp->hdr_len = sizeof(struct nvme_mi_admin_resp_hdr);
+ resp->data_len = 0;
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_err_nvme_resp(nvme_mi_ep_t ep)
+{
+ struct nvme_id_ctrl id;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_err_nvme_resp_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
+ assert(rc != 0);
+ assert(nvme_status_get_type(rc) == NVME_STATUS_TYPE_NVME);
+ assert(nvme_status_get_value(rc) ==
+ (NVME_SC_INTERNAL | (NVME_SCT_GENERIC << NVME_SCT_SHIFT)
+ | NVME_SC_DNR));
+}
+
+/* invalid Admin command transfers */
+static int test_admin_invalid_formats_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ /* none of the tests should result in message transfer */
+ assert(0);
+ return -1;
+}
+
+static void test_admin_invalid_formats(nvme_mi_ep_t ep)
+{
+ struct {
+ struct nvme_mi_admin_req_hdr hdr;
+ uint8_t data[4];
+ } req = { 0 };
+ struct nvme_mi_admin_resp_hdr resp = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ size_t len;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_invalid_formats_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 1);
+ assert(ctrl);
+
+ /* unaligned req size */
+ len = 0;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 1, &resp, 0, &len);
+ assert(rc != 0);
+
+ /* unaligned resp size */
+ len = 1;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 0, &resp, 0, &len);
+ assert(rc != 0);
+
+ /* unaligned resp offset */
+ len = 4;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 0, &resp, 1, &len);
+ assert(rc != 0);
+
+ /* resp too large */
+ len = 4096 + 4;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 0, &resp, 0, &len);
+ assert(rc != 0);
+
+ /* resp offset too large */
+ len = 4;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 0, &resp, (off_t)1 << 32, &len);
+ assert(rc != 0);
+
+ /* resp offset with no len */
+ len = 0;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 0, &resp, 4, &len);
+ assert(rc != 0);
+
+ /* req and resp payloads */
+ len = 4;
+ rc = nvme_mi_admin_xfer(ctrl, &req.hdr, 4, &resp, 0, &len);
+ assert(rc != 0);
+}
+
+/* test: header length too small */
+static int test_resp_hdr_small_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ resp->hdr_len = 2;
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_resp_hdr_small(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_resp_hdr_small_cb, NULL);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+/* test: respond with a request message */
+static int test_resp_req_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ resp->hdr->nmp &= ~(NVME_MI_ROR_RSP << 7);
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_resp_req(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_resp_req_cb, NULL);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+/* test: invalid MCTP type in response */
+static int test_resp_invalid_type_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ resp->hdr->type = 0x3;
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_resp_invalid_type(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_resp_invalid_type_cb, NULL);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+/* test: response with mis-matching command slot */
+static int test_resp_csi_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ resp->hdr->nmp ^= 0x1;
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_resp_csi(nvme_mi_ep_t ep)
+{
+ struct nvme_mi_read_nvm_ss_info ss_info;
+ int rc;
+
+ test_set_transport_callback(ep, test_resp_csi_cb, NULL);
+
+ rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+ assert(rc != 0);
+}
+
+/* test: config get MTU request & response layout, ensure we're handling
+ * endianness in the 3-byte NMRESP field correctly */
+static int test_mi_config_get_mtu_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_mi_mi_resp_hdr *mi_resp;
+ uint8_t *buf;
+
+ assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
+ assert(req->data_len == 0);
+
+ /* validate req as raw bytes */
+ buf = (void *)req->hdr;
+ assert(buf[4] == nvme_mi_mi_opcode_configuration_get);
+ /* dword 0: port and config id */
+ assert(buf[11] == 0x5);
+ assert(buf[8] == NVME_MI_CONFIG_MCTP_MTU);
+
+ /* set MTU in response */
+ mi_resp = (void *)resp->hdr;
+ mi_resp->nmresp[1] = 0x12;
+ mi_resp->nmresp[0] = 0x34;
+ resp->hdr_len = sizeof(*mi_resp);
+ resp->data_len = 0;
+
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_mi_config_get_mtu(nvme_mi_ep_t ep)
+{
+ uint16_t mtu;
+ int rc;
+
+ test_set_transport_callback(ep, test_mi_config_get_mtu_cb, NULL);
+
+ rc = nvme_mi_mi_config_get_mctp_mtu(ep, 5, &mtu);
+ assert(rc == 0);
+ assert(mtu == 0x1234);
+}
+
+/* test: config set SMBus freq, both valid and invalid */
+static int test_mi_config_set_freq_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_mi_mi_resp_hdr *mi_resp;
+ uint8_t *buf;
+
+ assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
+ assert(req->data_len == 0);
+
+ /* validate req as raw bytes */
+ buf = (void *)req->hdr;
+ assert(buf[4] == nvme_mi_mi_opcode_configuration_set);
+ /* dword 0: port and config id */
+ assert(buf[11] == 0x5);
+ assert(buf[8] == NVME_MI_CONFIG_SMBUS_FREQ);
+
+ mi_resp = (void *)resp->hdr;
+ resp->hdr_len = sizeof(*mi_resp);
+ resp->data_len = 0;
+
+ /* accept 100 & 400, reject others */
+ switch (buf[9]) {
+ case NVME_MI_CONFIG_SMBUS_FREQ_100kHz:
+ case NVME_MI_CONFIG_SMBUS_FREQ_400kHz:
+ mi_resp->status = 0;
+ break;
+ case NVME_MI_CONFIG_SMBUS_FREQ_1MHz:
+ default:
+ mi_resp->status = 0x4;
+ break;
+ }
+
+ test_transport_resp_calc_mic(resp);
+ return 0;
+}
+
+static void test_mi_config_set_freq(nvme_mi_ep_t ep)
+{
+ int rc;
+
+ test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL);
+
+ rc = nvme_mi_mi_config_set_smbus_freq(ep, 5,
+ NVME_MI_CONFIG_SMBUS_FREQ_100kHz);
+ assert(rc == 0);
+}
+
+static void test_mi_config_set_freq_invalid(nvme_mi_ep_t ep)
+{
+ int rc;
+
+ test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL);
+
+ rc = nvme_mi_mi_config_set_smbus_freq(ep, 5,
+ NVME_MI_CONFIG_SMBUS_FREQ_1MHz);
+ assert(rc == 4);
+}
+
+/* Get Features callback, implementing Arbitration (which doesn't return
+ * additional data) and Timestamp (which does).
+ */
+static int test_admin_get_features_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 sel, fid, ror, mt, *rq_hdr, *rs_hdr, *rs_data;
+ __u16 ctrl_id;
+ int i;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_ADMIN);
+
+ /* do we have enough for a mi header? */
+ assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
+
+ /* inspect response as raw bytes */
+ rq_hdr = (__u8 *)req->hdr;
+
+ /* opcode */
+ assert(rq_hdr[4] == nvme_admin_get_features);
+
+ /* controller */
+ ctrl_id = rq_hdr[7] << 8 | rq_hdr[6];
+ assert(ctrl_id == 0x5); /* controller id */
+
+ /* sel & fid from lower bytes of cdw10 */
+ fid = rq_hdr[44];
+ sel = rq_hdr[45] & 0x7;
+
+ /* reserved fields */
+ assert(!(rq_hdr[46] || rq_hdr[47] || rq_hdr[45] & 0xf8));
+
+ assert(sel == 0x00);
+
+ rs_hdr = (__u8 *)resp->hdr;
+ rs_hdr[4] = 0x00; /* status: success */
+ rs_data = resp->data;
+
+ /* feature-id specific checks, and response generation */
+ switch (fid) {
+ case NVME_FEAT_FID_ARBITRATION:
+ /* arbitrary (hah!) arbitration value in cdw0 of response */
+ rs_hdr[8] = 1;
+ rs_hdr[9] = 2;
+ rs_hdr[10] = 3;
+ rs_hdr[11] = 4;
+ resp->data_len = 0;
+ break;
+
+ case NVME_FEAT_FID_TIMESTAMP:
+ resp->data_len = 8;
+ for (i = 0; i < 6; i++)
+ rs_data[i] = i;
+ rs_data[6] = 1;
+ break;
+
+ default:
+ assert(0);
+ }
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_get_features_nodata(nvme_mi_ep_t ep)
+{
+ struct nvme_get_features_args args = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ uint32_t res;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_get_features_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ args.args_size = sizeof(args);
+ args.fid = NVME_FEAT_FID_ARBITRATION;
+ args.sel = 0;
+ args.result = &res;
+
+ rc = nvme_mi_admin_get_features(ctrl, &args);
+ assert(rc == 0);
+ assert(args.data_len == 0);
+ assert(res == 0x04030201);
+}
+
+static void test_get_features_data(nvme_mi_ep_t ep)
+{
+ struct nvme_get_features_args args = { 0 };
+ struct nvme_timestamp tstamp;
+ nvme_mi_ctrl_t ctrl;
+ uint8_t exp[6];
+ uint32_t res;
+ int rc, i;
+
+ test_set_transport_callback(ep, test_admin_get_features_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ args.args_size = sizeof(args);
+ args.fid = NVME_FEAT_FID_TIMESTAMP;
+ args.sel = 0;
+ args.result = &res;
+ args.data = &tstamp;
+ args.data_len = sizeof(tstamp);
+
+ /* expected timestamp value */
+ for (i = 0; i < sizeof(tstamp.timestamp); i++)
+ exp[i] = i;
+
+ rc = nvme_mi_admin_get_features(ctrl, &args);
+ assert(rc == 0);
+ assert(args.data_len == sizeof(tstamp));
+ assert(tstamp.attr == 1);
+ assert(!memcmp(tstamp.timestamp, exp, sizeof(tstamp.timestamp)));
+}
+
+/* Set Features callback for timestamp */
+static int test_admin_set_features_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 save, fid, ror, mt, *rq_hdr, *rq_data, *rs_hdr;
+ __u16 ctrl_id;
+ uint8_t ts[6];
+ int i;
+
+ assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
+
+ ror = req->hdr->nmp >> 7;
+ mt = req->hdr->nmp >> 3 & 0x7;
+ assert(ror == NVME_MI_ROR_REQ);
+ assert(mt == NVME_MI_MT_ADMIN);
+ assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
+ assert(req->data_len == 8);
+
+ rq_hdr = (__u8 *)req->hdr;
+ rq_data = req->data;
+
+ /* opcode */
+ assert(rq_hdr[4] == nvme_admin_set_features);
+
+ /* controller */
+ ctrl_id = rq_hdr[7] << 8 | rq_hdr[6];
+ assert(ctrl_id == 0x5); /* controller id */
+
+ /* fid from lower bytes of cdw10, save from top bit */
+ fid = rq_hdr[44];
+ save = rq_hdr[47] & 0x80;
+
+ /* reserved fields */
+ assert(!(rq_hdr[45] || rq_hdr[46]));
+
+ assert(fid == NVME_FEAT_FID_TIMESTAMP);
+ assert(save == 0x80);
+
+ for (i = 0; i < sizeof(ts); i++)
+ ts[i] = i;
+ assert(!memcmp(ts, rq_data, sizeof(ts)));
+
+ rs_hdr = (__u8 *)resp->hdr;
+ rs_hdr[4] = 0x00;
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_set_features(nvme_mi_ep_t ep)
+{
+ struct nvme_set_features_args args = { 0 };
+ struct nvme_timestamp tstamp = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ uint32_t res;
+ int rc, i;
+
+ test_set_transport_callback(ep, test_admin_set_features_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ for (i = 0; i < sizeof(tstamp.timestamp); i++)
+ tstamp.timestamp[i] = i;
+
+ args.args_size = sizeof(args);
+ args.fid = NVME_FEAT_FID_TIMESTAMP;
+ args.save = 1;
+ args.result = &res;
+ args.data = &tstamp;
+ args.data_len = sizeof(tstamp);
+
+ rc = nvme_mi_admin_set_features(ctrl, &args);
+ assert(rc == 0);
+ assert(args.data_len == 0);
+}
+
+enum ns_type {
+ NS_ACTIVE,
+ NS_ALLOC,
+};
+
+static int test_admin_id_ns_list_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_ns_list *list;
+ enum ns_type type;
+ int offset;
+ __u8 *hdr;
+ __u16 cns;
+
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ assert(req->data_len == 0);
+
+ cns = hdr[45] << 8 | hdr[44];
+
+ /* NSID */
+ assert(hdr[8] == 1 && !hdr[9] && !hdr[10] && !hdr[11]);
+
+ type = *(enum ns_type *)data;
+ resp->data_len = sizeof(*list);
+ list = resp->data;
+
+ switch (type) {
+ case NS_ALLOC:
+ assert(cns == NVME_IDENTIFY_CNS_ALLOCATED_NS_LIST);
+ offset = 2;
+ break;
+ case NS_ACTIVE:
+ assert(cns == NVME_IDENTIFY_CNS_NS_ACTIVE_LIST);
+ offset = 4;
+ break;
+ default:
+ assert(0);
+ }
+
+ list->ns[0] = cpu_to_le32(offset);
+ list->ns[1] = cpu_to_le32(offset + 1);
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_id_alloc_ns_list(struct nvme_mi_ep *ep)
+{
+ struct nvme_ns_list list;
+ nvme_mi_ctrl_t ctrl;
+ enum ns_type type;
+ int rc;
+
+ type = NS_ALLOC;
+ test_set_transport_callback(ep, test_admin_id_ns_list_cb, &type);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_allocated_ns_list(ctrl, 1, &list);
+ assert(!rc);
+
+ assert(le32_to_cpu(list.ns[0]) == 2);
+ assert(le32_to_cpu(list.ns[1]) == 3);
+ assert(le32_to_cpu(list.ns[2]) == 0);
+}
+
+static void test_admin_id_active_ns_list(struct nvme_mi_ep *ep)
+{
+ struct nvme_ns_list list;
+ nvme_mi_ctrl_t ctrl;
+ enum ns_type type;
+ int rc;
+
+ type = NS_ACTIVE;
+ test_set_transport_callback(ep, test_admin_id_ns_list_cb, &type);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_active_ns_list(ctrl, 1, &list);
+ assert(!rc);
+
+ assert(le32_to_cpu(list.ns[0]) == 4);
+ assert(le32_to_cpu(list.ns[1]) == 5);
+ assert(le32_to_cpu(list.ns[2]) == 0);
+}
+
+static int test_admin_id_ns_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_id_ns *id;
+ enum ns_type type;
+ __u16 nsid, cns;
+ __u8 *hdr;
+
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ assert(req->data_len == 0);
+
+ cns = hdr[45] << 8 | hdr[44];
+
+ /* NSID */
+ nsid = hdr[8];
+ assert(!hdr[9] && !hdr[10] && !hdr[11]);
+
+ type = *(enum ns_type *)data;
+ resp->data_len = sizeof(*id);
+ id = resp->data;
+ id->nsze = cpu_to_le64(nsid);
+
+ switch (type) {
+ case NS_ALLOC:
+ assert(cns == NVME_IDENTIFY_CNS_ALLOCATED_NS);
+ break;
+ case NS_ACTIVE:
+ assert(cns == NVME_IDENTIFY_CNS_NS);
+ break;
+ default:
+ assert(0);
+ }
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_id_alloc_ns(struct nvme_mi_ep *ep)
+{
+ struct nvme_id_ns id;
+ nvme_mi_ctrl_t ctrl;
+ enum ns_type type;
+ int rc;
+
+ type = NS_ALLOC;
+ test_set_transport_callback(ep, test_admin_id_ns_cb, &type);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_allocated_ns(ctrl, 1, &id);
+ assert(!rc);
+ assert(le64_to_cpu(id.nsze) == 1);
+}
+
+static void test_admin_id_active_ns(struct nvme_mi_ep *ep)
+{
+ struct nvme_id_ns id;
+ nvme_mi_ctrl_t ctrl;
+ enum ns_type type;
+ int rc;
+
+ type = NS_ACTIVE;
+ test_set_transport_callback(ep, test_admin_id_ns_cb, &type);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_ns(ctrl, 1, &id);
+ assert(!rc);
+ assert(le64_to_cpu(id.nsze) == 1);
+}
+
+static int test_admin_id_nsid_ctrl_list_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u16 cns, ctrlid;
+ __u32 nsid;
+ __u8 *hdr;
+
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ assert(req->data_len == 0);
+
+ cns = hdr[45] << 8 | hdr[44];
+ assert(cns == NVME_IDENTIFY_CNS_NS_CTRL_LIST);
+
+ nsid = hdr[11] << 24 | hdr[10] << 16 | hdr[9] << 8 | hdr[8];
+ assert(nsid == 0x01020304);
+
+ ctrlid = hdr[47] << 8 | hdr[46];
+ assert(ctrlid == 5);
+
+ resp->data_len = sizeof(struct nvme_ctrl_list);
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_id_nsid_ctrl_list(struct nvme_mi_ep *ep)
+{
+ struct nvme_ctrl_list list;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_id_nsid_ctrl_list_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_nsid_ctrl_list(ctrl, 0x01020304, 5, &list);
+ assert(!rc);
+}
+
+static int test_admin_id_secondary_ctrl_list_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u16 cns, ctrlid;
+ __u8 *hdr;
+
+ hdr = (__u8 *)req->hdr;
+ assert(hdr[4] == nvme_admin_identify);
+
+ assert(req->data_len == 0);
+
+ cns = hdr[45] << 8 | hdr[44];
+ assert(cns == NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST);
+
+ ctrlid = hdr[47] << 8 | hdr[46];
+ assert(ctrlid == 5);
+
+ resp->data_len = sizeof(struct nvme_secondary_ctrl_list);
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_id_secondary_ctrl_list(struct nvme_mi_ep *ep)
+{
+ struct nvme_secondary_ctrl_list list;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_id_secondary_ctrl_list_cb,
+ NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_identify_secondary_ctrl_list(ctrl, 5, &list);
+ assert(!rc);
+}
+
+static int test_admin_ns_mgmt_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ __u8 *rq_hdr, *rs_hdr, sel, csi;
+ struct nvme_ns_mgmt_host_sw_specified *create_data;
+ __u32 nsid;
+
+ rq_hdr = (__u8 *)req->hdr;
+ assert(rq_hdr[4] == nvme_admin_ns_mgmt);
+
+ sel = rq_hdr[44];
+ csi = rq_hdr[45];
+ nsid = rq_hdr[11] << 24 | rq_hdr[10] << 16 | rq_hdr[9] << 8 | rq_hdr[8];
+
+ rs_hdr = (__u8 *)resp->hdr;
+
+ switch (sel) {
+ case NVME_NS_MGMT_SEL_CREATE:
+ assert(req->data_len == sizeof(struct nvme_ns_mgmt_host_sw_specified));
+ create_data = req->data;
+
+ /* No NSID on created namespaces */
+ assert(nsid == 0);
+ assert(csi == 0);
+
+ /* allow operations on nsze == 42, reject others */
+ if (le64_to_cpu(create_data->nsze) != 42) {
+ rs_hdr[4] = 0;
+ /* response cdw0 is created NSID */
+ rs_hdr[8] = 0x04;
+ rs_hdr[9] = 0x03;
+ rs_hdr[10] = 0x02;
+ rs_hdr[11] = 0x01;
+ } else {
+ rs_hdr[4] = NVME_MI_RESP_INVALID_PARAM;
+ }
+ break;
+
+ case NVME_NS_MGMT_SEL_DELETE:
+ assert(req->data_len == 0);
+ /* NSID required on delete */
+ assert(nsid == 0x05060708);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_ns_mgmt_create(struct nvme_mi_ep *ep)
+{
+ struct nvme_ns_mgmt_host_sw_specified data = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ __u32 ns;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_ns_mgmt_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_ns_mgmt_create(ctrl, NULL, 0, &ns, &data);
+ assert(!rc);
+ assert(ns == 0x01020304);
+
+ data.nsze = cpu_to_le64(42);
+ rc = nvme_mi_admin_ns_mgmt_create(ctrl, NULL, 0, &ns, &data);
+ assert(rc);
+}
+
+static void test_admin_ns_mgmt_delete(struct nvme_mi_ep *ep)
+{
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ test_set_transport_callback(ep, test_admin_ns_mgmt_cb, NULL);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_ns_mgmt_delete(ctrl, 0x05060708);
+ assert(!rc);
+}
+
+struct attach_op {
+ enum {
+ NS_ATTACH,
+ NS_DETACH,
+ } op;
+ struct nvme_ctrl_list *list;
+};
+
+static int test_admin_ns_attach_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct attach_op *op = data;
+ __u8 *rq_hdr, sel;
+ __u32 nsid;
+
+ rq_hdr = (__u8 *)req->hdr;
+ assert(rq_hdr[4] == nvme_admin_ns_attach);
+
+ sel = rq_hdr[44];
+ nsid = rq_hdr[11] << 24 | rq_hdr[10] << 16 | rq_hdr[9] << 8 | rq_hdr[8];
+
+ assert(req->data_len == sizeof(*op->list));
+
+ assert(nsid == 0x02030405);
+ switch (op->op) {
+ case NS_ATTACH:
+ assert(sel == NVME_NS_ATTACH_SEL_CTRL_ATTACH);
+ break;
+ case NS_DETACH:
+ assert(sel == NVME_NS_ATTACH_SEL_CTRL_DEATTACH);
+ break;
+ default:
+ assert(0);
+ }
+
+ assert(!memcmp(req->data, op->list, sizeof(*op->list)));
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_ns_attach(struct nvme_mi_ep *ep)
+{
+ struct nvme_ctrl_list list = { 0 };
+ struct attach_op aop;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ list.num = cpu_to_le16(2);
+ list.identifier[0] = 4;
+ list.identifier[1] = 5;
+
+ aop.op = NS_ATTACH;
+ aop.list = &list;
+
+ test_set_transport_callback(ep, test_admin_ns_attach_cb, &aop);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_ns_attach_ctrls(ctrl, 0x02030405, &list);
+ assert(!rc);
+}
+
+static void test_admin_ns_detach(struct nvme_mi_ep *ep)
+{
+ struct nvme_ctrl_list list = { 0 };
+ struct attach_op aop;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ list.num = cpu_to_le16(2);
+ list.identifier[0] = 6;
+ list.identifier[1] = 7;
+
+ aop.op = NS_DETACH;
+ aop.list = &list;
+
+ test_set_transport_callback(ep, test_admin_ns_attach_cb, &aop);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ rc = nvme_mi_admin_ns_detach_ctrls(ctrl, 0x02030405, &list);
+ assert(!rc);
+}
+
+struct fw_download_info {
+ uint32_t offset;
+ uint32_t len;
+ void *data;
+};
+
+static int test_admin_fw_download_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct fw_download_info *info = data;
+ __u32 len, off;
+ __u8 *rq_hdr;
+
+ rq_hdr = (__u8 *)req->hdr;
+ assert(rq_hdr[4] == nvme_admin_fw_download);
+
+ len = rq_hdr[47] << 24 | rq_hdr[46] << 16 | rq_hdr[45] << 8 | rq_hdr[44];
+ off = rq_hdr[51] << 24 | rq_hdr[50] << 16 | rq_hdr[49] << 8 | rq_hdr[48];
+
+ assert(off << 2 == info->offset);
+ assert(((len+1) << 2) == info->len);
+
+ /* ensure that the request len matches too */
+ assert(req->data_len == info->len);
+
+ assert(!memcmp(req->data, info->data, len));
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_fw_download(struct nvme_mi_ep *ep)
+{
+ struct nvme_fw_download_args args;
+ struct fw_download_info info;
+ unsigned char fw[4096];
+ nvme_mi_ctrl_t ctrl;
+ int rc, i;
+
+ for (i = 0; i < sizeof(fw); i++)
+ fw[i] = i % 0xff;
+
+ info.offset = 0;
+ info.len = 0;
+ info.data = fw;
+ args.data = fw;
+ args.args_size = sizeof(args);
+
+ test_set_transport_callback(ep, test_admin_fw_download_cb, &info);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ /* invalid (zero) len */
+ args.data_len = info.len = 1;
+ args.offset = info.offset = 0;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(rc);
+
+ /* invalid (unaligned) len */
+ args.data_len = info.len = 1;
+ args.offset = info.offset = 0;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(rc);
+
+ /* invalid offset */
+ args.data_len = info.len = 4;
+ args.offset = info.offset = 1;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(rc);
+
+ /* smallest len */
+ args.data_len = info.len = 4;
+ args.offset = info.offset = 0;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(!rc);
+
+ /* largest len */
+ args.data_len = info.len = 4096;
+ args.offset = info.offset = 0;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(!rc);
+
+ /* offset value */
+ args.data_len = info.len = 4096;
+ args.offset = info.offset = 4096;
+ rc = nvme_mi_admin_fw_download(ctrl, &args);
+ assert(!rc);
+}
+
+struct fw_commit_info {
+ __u8 bpid;
+ __u8 action;
+ __u8 slot;
+};
+
+static int test_admin_fw_commit_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct fw_commit_info *info = data;
+ __u8 bpid, action, slot;
+ __u8 *rq_hdr;
+
+ rq_hdr = (__u8 *)req->hdr;
+ assert(rq_hdr[4] == nvme_admin_fw_commit);
+
+ bpid = (rq_hdr[47] >> 7) & 0x1;
+ slot = rq_hdr[44] & 0x7;
+ action = (rq_hdr[44] >> 3) & 0x7;
+
+ assert(!!bpid == !!info->bpid);
+ assert(slot == info->slot);
+ assert(action == info->action);
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_fw_commit(struct nvme_mi_ep *ep)
+{
+ struct nvme_fw_commit_args args;
+ struct fw_commit_info info;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ args.args_size = sizeof(args);
+ info.bpid = args.bpid = 0;
+
+ test_set_transport_callback(ep, test_admin_fw_commit_cb, &info);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ /* all zeros */
+ info.bpid = args.bpid = 0;
+ info.slot = args.slot = 0;
+ info.action = args.action = 0;
+ rc = nvme_mi_admin_fw_commit(ctrl, &args);
+ assert(!rc);
+
+ /* all ones */
+ info.bpid = args.bpid = 1;
+ info.slot = args.slot = 0x7;
+ info.action = args.action = 0x7;
+ rc = nvme_mi_admin_fw_commit(ctrl, &args);
+ assert(!rc);
+
+ /* correct fields */
+ info.bpid = args.bpid = 1;
+ info.slot = args.slot = 2;
+ info.action = args.action = 3;
+ rc = nvme_mi_admin_fw_commit(ctrl, &args);
+ assert(!rc);
+}
+
+struct format_data {
+ __u32 nsid;
+ __u8 lbafu;
+ __u8 ses;
+ __u8 pil;
+ __u8 pi;
+ __u8 mset;
+ __u8 lbafl;
+};
+
+static int test_admin_format_nvm_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_format_nvm_args *args = data;
+ __u8 *rq_hdr;
+ __u32 nsid;
+
+ assert(req->data_len == 0);
+
+ rq_hdr = (__u8 *)req->hdr;
+
+ assert(rq_hdr[4] == nvme_admin_format_nvm);
+
+ nsid = (__u32)rq_hdr[11] << 24
+ | rq_hdr[10] << 16
+ | rq_hdr[9] << 8
+ | rq_hdr[8];
+ assert(nsid == args->nsid);
+
+ assert(((rq_hdr[44] >> 0) & 0xf) == args->lbaf);
+ assert(((rq_hdr[44] >> 4) & 0x1) == args->mset);
+ assert(((rq_hdr[44] >> 5) & 0x7) == args->pi);
+
+ assert(((rq_hdr[45] >> 0) & 0x1) == args->pil);
+ assert(((rq_hdr[45] >> 1) & 0x7) == args->ses);
+ assert(((rq_hdr[45] >> 4) & 0x3) == args->lbafu);
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_format_nvm(struct nvme_mi_ep *ep)
+{
+ struct nvme_format_nvm_args args = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ test_set_transport_callback(ep, test_admin_format_nvm_cb, &args);
+
+ /* ensure we have the cdw0 bit field encoding correct, by testing twice
+ * with inverted bit values */
+ args.args_size = sizeof(args);
+ args.nsid = 0x04030201;
+ args.lbafu = 0x3;
+ args.ses = 0x0;
+ args.pil = 0x1;
+ args.pi = 0x0;
+ args.mset = 0x1;
+ args.lbaf = 0x0;
+
+ rc = nvme_mi_admin_format_nvm(ctrl, &args);
+ assert(!rc);
+
+ args.nsid = ~args.nsid;
+ args.lbafu = 0;
+ args.ses = 0x7;
+ args.pil = 0x0;
+ args.pi = 0x7;
+ args.mset = 0x0;
+ args.lbaf = 0xf;
+
+ rc = nvme_mi_admin_format_nvm(ctrl, &args);
+ assert(!rc);
+}
+
+static int test_admin_sanitize_nvm_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ struct nvme_sanitize_nvm_args *args = data;
+ __u8 *rq_hdr;
+ __u32 ovrpat;
+
+ assert(req->data_len == 0);
+
+ rq_hdr = (__u8 *)req->hdr;
+
+ assert(rq_hdr[4] == nvme_admin_sanitize_nvm);
+
+ assert(((rq_hdr[44] >> 0) & 0x7) == args->sanact);
+ assert(((rq_hdr[44] >> 3) & 0x1) == args->ause);
+ assert(((rq_hdr[44] >> 4) & 0xf) == args->owpass);
+
+ assert(((rq_hdr[45] >> 0) & 0x1) == args->oipbp);
+ assert(((rq_hdr[45] >> 1) & 0x1) == args->nodas);
+
+ ovrpat = (__u32)rq_hdr[51] << 24 | rq_hdr[50] << 16 |
+ rq_hdr[49] << 8 | rq_hdr[48];
+ assert(ovrpat == args->ovrpat);
+
+ test_transport_resp_calc_mic(resp);
+
+ return 0;
+}
+
+static void test_admin_sanitize_nvm(struct nvme_mi_ep *ep)
+{
+ struct nvme_sanitize_nvm_args args = { 0 };
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+ assert(ctrl);
+
+ test_set_transport_callback(ep, test_admin_sanitize_nvm_cb, &args);
+
+ args.args_size = sizeof(args);
+ args.sanact = 0x7;
+ args.ause = 0x0;
+ args.owpass = 0xf;
+ args.oipbp = 0x0;
+ args.nodas = 0x1;
+ args.ovrpat = ~0x04030201;
+
+ rc = nvme_mi_admin_sanitize_nvm(ctrl, &args);
+ assert(!rc);
+
+ args.sanact = 0x0;
+ args.ause = 0x1;
+ args.owpass = 0x0;
+ args.oipbp = 0x1;
+ args.nodas = 0x0;
+ args.ovrpat = 0x04030201;
+
+ rc = nvme_mi_admin_sanitize_nvm(ctrl, &args);
+ assert(!rc);
+}
+
+/* test that we set the correct offset and size on get_log() calls that
+ * are split into multiple requests */
+struct log_data {
+ int n;
+};
+
+static int test_admin_get_log_split_cb(struct nvme_mi_ep *ep,
+ struct nvme_mi_req *req,
+ struct nvme_mi_resp *resp,
+ void *data)
+{
+ uint32_t log_page_offset_lower;
+ struct log_data *ldata = data;
+ uint32_t len, off;
+ __u8 *rq_hdr;
+
+ assert(req->data_len == 0);
+
+ rq_hdr = (__u8 *)req->hdr;
+
+ assert(rq_hdr[4] == nvme_admin_get_log_page);
+
+ /* from the MI message's DOFST/DLEN fields */
+ off = rq_hdr[31] << 24 | rq_hdr[30] << 16 | rq_hdr[29] << 8 | rq_hdr[28];
+ len = rq_hdr[35] << 24 | rq_hdr[34] << 16 | rq_hdr[33] << 8 | rq_hdr[32];
+
+ /* From the MI message's Command Dword 12 */
+ log_page_offset_lower = rq_hdr[55] << 24 | rq_hdr[54] << 16 | rq_hdr[53] << 8 | rq_hdr[52];
+
+ /* we should have a full-sized start and middle, and a short end */
+ switch (ldata->n) {
+ case 0:
+ assert(log_page_offset_lower == 0);
+ assert(len == 4096);
+ assert(off == 0);
+ break;
+ case 1:
+ assert(log_page_offset_lower == 4096);
+ assert(len == 4096);
+ assert(off == 0);
+ break;
+ case 2:
+ assert(log_page_offset_lower == 8192);
+ assert(len == 4);
+ assert(off == 0);
+ break;
+ default:
+ assert(0);
+ }
+
+ /* ensure we've sized the expected response correctly */
+ assert(resp->data_len == len);
+ memset(resp->data, ldata->n & 0xff, len);
+
+ test_transport_resp_calc_mic(resp);
+
+ ldata->n++;
+
+ return 0;
+}
+
+static void test_admin_get_log_split(struct nvme_mi_ep *ep)
+{
+ struct nvme_get_log_args args = { 0 };
+ unsigned char buf[4096 * 2 + 4];
+ struct log_data ldata;
+ nvme_mi_ctrl_t ctrl;
+ int rc;
+
+ ldata.n = 0;
+ test_set_transport_callback(ep, test_admin_get_log_split_cb, &ldata);
+
+ ctrl = nvme_mi_init_ctrl(ep, 5);
+
+ args.args_size = sizeof(args);
+ args.lid = 1;
+ args.log = buf;
+ args.len = sizeof(buf);
+ args.lpo = 0;
+ args.ot = false;
+
+ rc = nvme_mi_admin_get_log(ctrl, &args);
+
+ assert(!rc);
+
+ /* we should have sent three commands */
+ assert(ldata.n == 3);
+}
+
+#define DEFINE_TEST(name) { #name, test_ ## name }
+struct test {
+ const char *name;
+ void (*fn)(nvme_mi_ep_t);
+} tests[] = {
+ DEFINE_TEST(endpoint_lifetime),
+ DEFINE_TEST(ctrl_lifetime),
+ DEFINE_TEST(read_mi_data),
+ DEFINE_TEST(transport_fail),
+ DEFINE_TEST(transport_describe),
+ DEFINE_TEST(scan_ctrl_list),
+ DEFINE_TEST(invalid_crc),
+ DEFINE_TEST(admin_id),
+ DEFINE_TEST(admin_err_mi_resp),
+ DEFINE_TEST(admin_err_nvme_resp),
+ DEFINE_TEST(admin_invalid_formats),
+ DEFINE_TEST(resp_req),
+ DEFINE_TEST(resp_hdr_small),
+ DEFINE_TEST(resp_invalid_type),
+ DEFINE_TEST(resp_csi),
+ DEFINE_TEST(mi_config_get_mtu),
+ DEFINE_TEST(mi_config_set_freq),
+ DEFINE_TEST(mi_config_set_freq_invalid),
+ DEFINE_TEST(get_features_nodata),
+ DEFINE_TEST(get_features_data),
+ DEFINE_TEST(set_features),
+ DEFINE_TEST(admin_id_alloc_ns_list),
+ DEFINE_TEST(admin_id_active_ns_list),
+ DEFINE_TEST(admin_id_alloc_ns),
+ DEFINE_TEST(admin_id_active_ns),
+ DEFINE_TEST(admin_id_nsid_ctrl_list),
+ DEFINE_TEST(admin_id_secondary_ctrl_list),
+ DEFINE_TEST(admin_ns_mgmt_create),
+ DEFINE_TEST(admin_ns_mgmt_delete),
+ DEFINE_TEST(admin_ns_attach),
+ DEFINE_TEST(admin_ns_detach),
+ DEFINE_TEST(admin_fw_download),
+ DEFINE_TEST(admin_fw_commit),
+ DEFINE_TEST(admin_format_nvm),
+ DEFINE_TEST(admin_sanitize_nvm),
+ DEFINE_TEST(admin_get_log_split),
+};
+
+static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep)
+{
+ printf("Running test %s...", test->name);
+ fflush(stdout);
+ test->fn(ep);
+ /* tests will assert on failure; if we're here, we're OK */
+ printf(" OK\n");
+ test_print_log_buf(logfd);
+}
+
+int main(void)
+{
+ nvme_root_t root;
+ nvme_mi_ep_t ep;
+ unsigned int i;
+ FILE *fd;
+
+ fd = test_setup_log();
+
+ root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
+ assert(root);
+
+ ep = nvme_mi_open_test(root);
+ assert(ep);
+
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ run_test(&tests[i], fd, ep);
+ }
+
+ nvme_mi_close(ep);
+ nvme_mi_free_root(root);
+
+ test_close_log(fd);
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/mock-ifaddrs.c b/test/mock-ifaddrs.c
new file mode 100644
index 0000000..87a2e50
--- /dev/null
+++ b/test/mock-ifaddrs.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2023 Martin Belanger, Dell Technologies Inc.
+ */
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+struct ifaddrs_storage {
+ struct ifaddrs ifa;
+ union {
+ /* Reserve space for the biggest of the sockaddr types */
+ struct sockaddr_in s4;
+ struct sockaddr_in6 s6;
+ } addr, netmask, broadaddr;
+ char name[IF_NAMESIZE + 1];
+};
+
+static void init_entry(struct ifaddrs_storage *storage,
+ const char *ifname,
+ int family,
+ uint32_t addr1,
+ uint32_t addr2,
+ uint32_t addr3,
+ uint32_t addr4,
+ bool last)
+{
+ struct ifaddrs *p;
+
+ p = &storage->ifa;
+ p->ifa_next = last ? NULL : &storage[1].ifa;
+ p->ifa_name = storage->name;
+ strcpy(p->ifa_name, ifname);
+ p->ifa_flags = 0;
+
+ if (family == AF_INET) {
+ struct sockaddr_in *ipv4;
+
+ ipv4 = &storage->addr.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = htonl(addr1);
+ p->ifa_addr = (struct sockaddr *)ipv4;
+
+ ipv4 = &storage->netmask.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = 0xffffff00;
+ p->ifa_netmask = (struct sockaddr *)ipv4;
+
+ ipv4 = &storage->broadaddr.s4;
+ ipv4->sin_family = family;
+ ipv4->sin_port = 0;
+ ipv4->sin_addr.s_addr = 0;
+ p->ifa_broadaddr = (struct sockaddr *)ipv4;;
+ } else {
+ struct sockaddr_in6 *ipv6;
+
+ ipv6 = &storage->addr.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = htonl(addr1);
+ ipv6->sin6_addr.s6_addr32[1] = htonl(addr2);
+ ipv6->sin6_addr.s6_addr32[2] = htonl(addr3);
+ ipv6->sin6_addr.s6_addr32[3] = htonl(addr4);
+ ipv6->sin6_scope_id = 0;
+ p->ifa_addr = (struct sockaddr *)ipv6;
+
+ ipv6 = &storage->netmask.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[1] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[2] = 0xffffffff;
+ ipv6->sin6_addr.s6_addr32[3] = 0;
+ ipv6->sin6_scope_id = 0;
+ p->ifa_netmask = (struct sockaddr *)ipv6;
+
+ ipv6 = &storage->broadaddr.s6;
+ ipv6->sin6_family = family;
+ ipv6->sin6_port = 0;
+ ipv6->sin6_flowinfo = 0;
+ ipv6->sin6_addr.s6_addr32[0] = 0;
+ ipv6->sin6_addr.s6_addr32[1] = 0;
+ ipv6->sin6_addr.s6_addr32[2] = 0;
+ ipv6->sin6_addr.s6_addr32[3] = 0;
+ ipv6->sin6_scope_id = 0;
+ p->ifa_broadaddr = (struct sockaddr *)ipv6;
+ }
+
+ p->ifa_data = NULL;
+}
+
+int getifaddrs(struct ifaddrs **ifap) {
+ struct ifaddrs_storage *storage;
+
+ /* Allocate memory for 4 interfaces */
+ storage = (struct ifaddrs_storage *)calloc(4, sizeof(struct ifaddrs_storage));
+ *ifap = &storage[0].ifa;
+
+ init_entry(&storage[0], "eth0", AF_INET, 0xc0a80114, 0, 0, 0, false); /* 192.168.1.20 */
+ init_entry(&storage[1], "eth0", AF_INET6, 0xfe800000, 0, 0, 0xdeadbeef, false); /* fe80::dead:beef */
+
+ /* Loopback interface */
+ init_entry(&storage[2], "lo", AF_INET, 0x7f000001, 0, 0, 0, false); /* 127.0.0.1 */
+ init_entry(&storage[3], "lo", AF_INET6, 0, 0, 0, 1, true); /* ::1 */
+
+ return 0;
+}
+
+void freeifaddrs(struct ifaddrs *ifa) {
+ free(ifa);
+}
+
diff --git a/test/nbft/README b/test/nbft/README
new file mode 100644
index 0000000..0f252a5
--- /dev/null
+++ b/test/nbft/README
@@ -0,0 +1,17 @@
+This is a simple testcase to verify the NBFT parser output over a set
+of provided ACPI NBFT tables.
+
+The 'nbft-dump' test utility is a simple tool to print out all elements
+and nested arrays of the nbft_info structs, bearing only minimal logic.
+
+The 'tables' directory contains sample binary files taken from
+/sys/firmware/acpi/tables. The 'tables_bad' then contains experiments, older
+table revisions or malformed data to test the parser error path.
+
+The "diffs" directory contains reference output generated by the 'nbft-dump'
+utility that is being compared against actual testcase run output. Everytime
+'nbft-dump.c' is modified these reference output files need to be regenerated
+by calling `ninja -C .build nbft-diffs` over the configured meson project.
+
+The tests are typically ran as part of the standard `meson test -C .build`
+invocation.
diff --git a/test/nbft/diffs/NBFT-Dell.PowerEdge.R660-fw1.5.5-single b/test/nbft/diffs/NBFT-Dell.PowerEdge.R660-fw1.5.5-single
new file mode 100644
index 0000000..72a34de
--- /dev/null
+++ b/test/nbft/diffs/NBFT-Dell.PowerEdge.R660-fw1.5.5-single
@@ -0,0 +1,54 @@
+raw_nbft_size=930
+host.id=44454c4c44010448030b8c04f445833
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:4c4c4544-0044-4410-8030-b8c04f445833
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=27136
+hfi_list[0]->tcp_info.mac_addr=062bcbeb70
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=94
+hfi_list[0]->tcp_info.ipaddr=172.18.240.1
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.route_metric=500
+hfi_list[0]->tcp_info.primary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=(null)
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=172.18.240.60
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=270
+subsystem_ns_list[0]->nid_type=2
+subsystem_ns_list[0]->nid=5380b42fc0c5de718ccf9680be3ca7
+subsystem_ns_list[0]->subsys_nqn=nqn.1988-11.com.dell:powerstore:00:88b402df2d762AA7AF94
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=4105
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
+subsystem_ns_list[1]->index=2
+subsystem_ns_list[1]->num_hfis=1
+subsystem_ns_list[1]->hfis[0]->index=1
+subsystem_ns_list[1]->transport=tcp
+subsystem_ns_list[1]->traddr=172.18.240.60
+subsystem_ns_list[1]->trsvcid=4420
+subsystem_ns_list[1]->subsys_port_id=0
+subsystem_ns_list[1]->nsid=1671
+subsystem_ns_list[1]->nid_type=2
+subsystem_ns_list[1]->nid=f4c66fce74afdb8ccf96807eaeae
+subsystem_ns_list[1]->subsys_nqn=nqn.1988-11.com.dell:powerstore:00:88b402df2d762AA7AF94
+subsystem_ns_list[1]->pdu_header_digest_required=0
+subsystem_ns_list[1]->data_digest_required=0
+subsystem_ns_list[1]->controller_id=4105
+subsystem_ns_list[1]->asqsz=0
+subsystem_ns_list[1]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-Dell.PowerEdge.R760 b/test/nbft/diffs/NBFT-Dell.PowerEdge.R760
new file mode 100644
index 0000000..d7fab3f
--- /dev/null
+++ b/test/nbft/diffs/NBFT-Dell.PowerEdge.R760
@@ -0,0 +1,60 @@
+raw_nbft_size=1017
+host.id=44454c4c34010368038b2c04f313233
+host.nqn=nqn.1988-11.com.dell:PowerEdge.R760.1234567
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=16384
+hfi_list[0]->tcp_info.mac_addr=b02628e87ce
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=82
+hfi_list[0]->tcp_info.ipaddr=100.71.245.232
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=100.71.245.254
+hfi_list[0]->tcp_info.route_metric=500
+hfi_list[0]->tcp_info.primary_dns_ipaddr=100.64.0.5
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=100.64.0.6
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=100.71.245.254
+hfi_list[0]->tcp_info.host_name=(null)
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=1
+discovery_list[0]->index=1
+discovery_list[0]->hfi->index=1
+discovery_list[0]->uri=nvme+tcp://100.71.103.50:8009/
+discovery_list[0]->nqn=nqn.2014-08.org.nvmexpress.discovery
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->discovery->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=100.71.103.48
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=148
+subsystem_ns_list[0]->nid_type=2
+subsystem_ns_list[0]->nid=c8244ed9c15f53b8ccf96802efca
+subsystem_ns_list[0]->subsys_nqn=nqn.1988-11.com.dell:powerstore:00:2a64abf1c5b81F6C4549
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=5
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
+subsystem_ns_list[1]->index=2
+subsystem_ns_list[1]->discovery->index=1
+subsystem_ns_list[1]->num_hfis=1
+subsystem_ns_list[1]->hfis[0]->index=1
+subsystem_ns_list[1]->transport=tcp
+subsystem_ns_list[1]->traddr=100.71.103.49
+subsystem_ns_list[1]->trsvcid=4420
+subsystem_ns_list[1]->subsys_port_id=0
+subsystem_ns_list[1]->nsid=148
+subsystem_ns_list[1]->nid_type=2
+subsystem_ns_list[1]->nid=c8244ed9c15f53b8ccf96802efca
+subsystem_ns_list[1]->subsys_nqn=nqn.1988-11.com.dell:powerstore:00:2a64abf1c5b81F6C4549
+subsystem_ns_list[1]->pdu_header_digest_required=0
+subsystem_ns_list[1]->data_digest_required=0
+subsystem_ns_list[1]->controller_id=4166
+subsystem_ns_list[1]->asqsz=0
+subsystem_ns_list[1]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-auto-ipv6 b/test/nbft/diffs/NBFT-auto-ipv6
new file mode 100644
index 0000000..83ee643
--- /dev/null
+++ b/test/nbft/diffs/NBFT-auto-ipv6
@@ -0,0 +1,38 @@
+raw_nbft_size=721
+host.id=1ee8b170eb4c864fb7957d179e201a
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:70b1e81e-4ceb-4f86-b709-57d1079e201a
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=512
+hfi_list[0]->tcp_info.mac_addr=525409e201a
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=1
+hfi_list[0]->tcp_info.ipaddr=fd09:9a46:b5c1:1ff:5054:ff:fe9e:201a
+hfi_list[0]->tcp_info.subnet_mask_prefix=64
+hfi_list[0]->tcp_info.gateway_ipaddr=::
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=::
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=::
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=nvmepoc-sles15-sp5
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=fd09:9a46:b5c1:1ff:5054:ff:fefd:9e66
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=848f4dc06d394968bf180569b8eea97
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.de.suse.mwilck:zeus.vagrant-nvmet.subsys04
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=1
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-dhcp-ipv4 b/test/nbft/diffs/NBFT-dhcp-ipv4
new file mode 100644
index 0000000..067079d
--- /dev/null
+++ b/test/nbft/diffs/NBFT-dhcp-ipv4
@@ -0,0 +1,43 @@
+raw_nbft_size=825
+host.id=e359b7a15d37747b3cc1754b8b819b9
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:a1b759e3-035d-4777-b3cc-1754b8b819b9
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=8
+hfi_list[0]->tcp_info.mac_addr=52540b819b9
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=3
+hfi_list[0]->tcp_info.ipaddr=192.168.49.155
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=192.168.49.1
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=192.168.49.1
+hfi_list[0]->tcp_info.host_name=nvmeof-sles
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=1
+discovery_list[0]->index=1
+discovery_list[0]->hfi->index=1
+discovery_list[0]->uri=nvme+tcp://192.168.49.10:4420/
+discovery_list[0]->nqn=nqn.2014-08.org.nvmexpress.discovery
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->discovery->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=192.168.49.10
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=df669a88bd6f4dd68a505f97eb55c835
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.org.nvmexpress.boot.poc:bremer.vagrant-nvmet.subsys02
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=7
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-dhcp-ipv6 b/test/nbft/diffs/NBFT-dhcp-ipv6
new file mode 100644
index 0000000..11c974f
--- /dev/null
+++ b/test/nbft/diffs/NBFT-dhcp-ipv6
@@ -0,0 +1,38 @@
+raw_nbft_size=725
+host.id=e359b7a15d37747b3cc1754b8b819b9
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:a1b759e3-035d-4777-b3cc-1754b8b819b9
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=8
+hfi_list[0]->tcp_info.mac_addr=52540b819b9
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=3
+hfi_list[0]->tcp_info.ipaddr=fddf:d:f:49::eb
+hfi_list[0]->tcp_info.subnet_mask_prefix=64
+hfi_list[0]->tcp_info.gateway_ipaddr=::
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=::
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=::
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=::
+hfi_list[0]->tcp_info.host_name=nvmeof-sles
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=1
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=fddf:d:f:49::10
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=df669a88bd6f4dd68a505f97eb55c835
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.org.nvmexpress.boot.poc:bremer.vagrant-nvmet.subsys02
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=34
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-rhpoc b/test/nbft/diffs/NBFT-rhpoc
new file mode 100644
index 0000000..d849b6e
--- /dev/null
+++ b/test/nbft/diffs/NBFT-rhpoc
@@ -0,0 +1,38 @@
+raw_nbft_size=724
+host.id=b4bb164e7f9be448c7f77d8b4fc9f39
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:f8131bac-cdef-4165-866b-5998c1e67890
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=40
+hfi_list[0]->tcp_info.mac_addr=eaebd3588958
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=1
+hfi_list[0]->tcp_info.ipaddr=192.168.101.30
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=host-vm
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=192.168.101.20
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=bee9c2b7176144b5a4e6f69498a94b
+subsystem_ns_list[0]->subsys_nqn=nqn.2014-08.org.nvmexpress:uuid:0c468c4d-a385-47e0-8299-6e95051277db
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=12
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-static-ipv4 b/test/nbft/diffs/NBFT-static-ipv4
new file mode 100644
index 0000000..a6f3859
--- /dev/null
+++ b/test/nbft/diffs/NBFT-static-ipv4
@@ -0,0 +1,38 @@
+raw_nbft_size=725
+host.id=e359b7a15d37747b3cc1754b8b819b9
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:a1b759e3-035d-4777-b3cc-1754b8b819b9
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=8
+hfi_list[0]->tcp_info.mac_addr=52540b819b9
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=1
+hfi_list[0]->tcp_info.ipaddr=192.168.49.50
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=nvmeof-sles
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=192.168.49.10
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=df669a88bd6f4dd68a505f97eb55c835
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.org.nvmexpress.boot.poc:bremer.vagrant-nvmet.subsys02
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=38
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-static-ipv4-discovery b/test/nbft/diffs/NBFT-static-ipv4-discovery
new file mode 100644
index 0000000..5bf0e46
--- /dev/null
+++ b/test/nbft/diffs/NBFT-static-ipv4-discovery
@@ -0,0 +1,43 @@
+raw_nbft_size=825
+host.id=e359b7a15d37747b3cc1754b8b819b9
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:a1b759e3-035d-4777-b3cc-1754b8b819b9
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=8
+hfi_list[0]->tcp_info.mac_addr=52540b819b9
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=1
+hfi_list[0]->tcp_info.ipaddr=192.168.49.50
+hfi_list[0]->tcp_info.subnet_mask_prefix=24
+hfi_list[0]->tcp_info.gateway_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=0.0.0.0
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=nvmeof-sles
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+discovery_list[0]->index=1
+discovery_list[0]->hfi->index=1
+discovery_list[0]->uri=nvme+tcp://192.168.49.10:4420/
+discovery_list[0]->nqn=nqn.2014-08.org.nvmexpress.discovery
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->discovery->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=192.168.49.10
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=df669a88bd6f4dd68a505f97eb55c835
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.org.nvmexpress.boot.poc:bremer.vagrant-nvmet.subsys02
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=13
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/diffs/NBFT-static-ipv6 b/test/nbft/diffs/NBFT-static-ipv6
new file mode 100644
index 0000000..c6ad844
--- /dev/null
+++ b/test/nbft/diffs/NBFT-static-ipv6
@@ -0,0 +1,38 @@
+raw_nbft_size=721
+host.id=1ee8b170eb4c864fb7957d179e201a
+host.nqn=nqn.2014-08.org.nvmexpress:uuid:70b1e81e-4ceb-4f86-b709-57d1079e201a
+host.host_id_configured=1
+host.host_nqn_configured=1
+host.primary=0
+hfi_list[0]->index=1
+hfi_list[0]->transport=tcp
+hfi_list[0]->tcp_info.pci_sbdf=512
+hfi_list[0]->tcp_info.mac_addr=525409e201a
+hfi_list[0]->tcp_info.vlan=0
+hfi_list[0]->tcp_info.ip_origin=1
+hfi_list[0]->tcp_info.ipaddr=fd09:9a46:b5c1:1fe::10
+hfi_list[0]->tcp_info.subnet_mask_prefix=64
+hfi_list[0]->tcp_info.gateway_ipaddr=::
+hfi_list[0]->tcp_info.route_metric=0
+hfi_list[0]->tcp_info.primary_dns_ipaddr=::
+hfi_list[0]->tcp_info.secondary_dns_ipaddr=::
+hfi_list[0]->tcp_info.dhcp_server_ipaddr=
+hfi_list[0]->tcp_info.host_name=nvmepoc-sles15-sp5
+hfi_list[0]->tcp_info.this_hfi_is_default_route=1
+hfi_list[0]->tcp_info.dhcp_override=0
+subsystem_ns_list[0]->index=1
+subsystem_ns_list[0]->num_hfis=1
+subsystem_ns_list[0]->hfis[0]->index=1
+subsystem_ns_list[0]->transport=tcp
+subsystem_ns_list[0]->traddr=fd09:9a46:b5c1:1fe::13f
+subsystem_ns_list[0]->trsvcid=4420
+subsystem_ns_list[0]->subsys_port_id=0
+subsystem_ns_list[0]->nsid=1
+subsystem_ns_list[0]->nid_type=3
+subsystem_ns_list[0]->nid=aab2c3c8444c47c599f23632e6364528
+subsystem_ns_list[0]->subsys_nqn=nqn.2022-12.de.suse.mwilck:zeus.vagrant-nvmet.subsys04
+subsystem_ns_list[0]->pdu_header_digest_required=0
+subsystem_ns_list[0]->data_digest_required=0
+subsystem_ns_list[0]->controller_id=9
+subsystem_ns_list[0]->asqsz=0
+subsystem_ns_list[0]->dhcp_root_path_string=(null)
diff --git a/test/nbft/gen-nbft-diffs.sh.in b/test/nbft/gen-nbft-diffs.sh.in
new file mode 100755
index 0000000..8b0b982
--- /dev/null
+++ b/test/nbft/gen-nbft-diffs.sh.in
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+for T in "@TABLES_DIR@"/*; do
+ "@NBFT_DUMP_PATH@" "$T" > "@DIFF_DIR@/`basename $T`"
+done
diff --git a/test/nbft/meson.build b/test/nbft/meson.build
new file mode 100644
index 0000000..919bf83
--- /dev/null
+++ b/test/nbft/meson.build
@@ -0,0 +1,85 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of libnvme.
+# Copyright (c) 2023 Red Hat Inc.
+#
+# Authors: Tomas Bzatek <tbzatek@redhat.com>
+
+# NBFT parser tests over supplied NBFT ACPI table dumps
+
+tables_dir = 'tables'
+tables_bad_dir = 'tables_bad'
+diff_dir = 'diffs'
+
+tables = [
+ 'NBFT-auto-ipv6',
+ 'NBFT-dhcp-ipv6',
+ 'NBFT-rhpoc',
+ 'NBFT-static-ipv4',
+ 'NBFT-static-ipv4-discovery',
+ 'NBFT-static-ipv6',
+ 'NBFT-Dell.PowerEdge.R760',
+ 'NBFT-Dell.PowerEdge.R660-fw1.5.5-single'
+]
+
+tables_bad = [
+ 'NBFT-bad-oldspec',
+ 'NBFT-random-noise',
+]
+
+nbft_dump = executable(
+ 'nbft-dump',
+ ['nbft-dump.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+
+helper_data = configuration_data()
+helper_data.set('NBFT_DUMP_PATH', nbft_dump.full_path())
+helper_data.set('TABLES_DIR', meson.current_source_dir()/tables_dir)
+helper_data.set('DIFF_DIR', meson.current_source_dir()/diff_dir)
+
+dump_helper = configure_file(
+ input: 'nbft-dump-diff.sh.in',
+ output: '@BASENAME@',
+ configuration: helper_data
+)
+
+gen_diffs_helper = configure_file(
+ input: 'gen-nbft-diffs.sh.in',
+ output: '@BASENAME@',
+ configuration: helper_data
+)
+
+
+run_target(
+ 'nbft-diffs',
+ depends: nbft_dump,
+ command: [gen_diffs_helper]
+)
+
+
+diffcmd = find_program(
+ 'diff',
+ required: false
+)
+if diffcmd.found()
+ foreach table: tables
+ test(
+ table,
+ dump_helper,
+ args: [files(tables_dir/table),
+ files(diff_dir/table)]
+ )
+ endforeach
+endif
+
+foreach table: tables_bad
+ test(
+ table,
+ nbft_dump,
+ args: [files(tables_bad_dir/table)],
+ should_fail: true
+ )
+endforeach
diff --git a/test/nbft/nbft-dump-diff.sh.in b/test/nbft/nbft-dump-diff.sh.in
new file mode 100755
index 0000000..f697bce
--- /dev/null
+++ b/test/nbft/nbft-dump-diff.sh.in
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ $# -ne 2 ]; then
+ echo "Usage: $0 TABLE DIFF" >&2
+ exit 255
+fi
+
+"@NBFT_DUMP_PATH@" "$1" | diff --unified "$2" -
diff --git a/test/nbft/nbft-dump.c b/test/nbft/nbft-dump.c
new file mode 100644
index 0000000..3ff5efa
--- /dev/null
+++ b/test/nbft/nbft-dump.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2023 Red Hat Inc.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include "libnvme.h"
+
+static void print_hex(unsigned char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++, buf++)
+ printf("%x", *buf);
+}
+
+static void print_nbft(struct nbft_info *table)
+{
+ unsigned int i, j;
+ struct nbft_info_hfi **hfi;
+ struct nbft_info_security **sec;
+ struct nbft_info_discovery **disc;
+ struct nbft_info_subsystem_ns **ssns;
+
+ printf("raw_nbft_size=%zd\n", table->raw_nbft_size);
+
+ printf("host.id=");
+ print_hex(table->host.id, NVME_UUID_LEN);
+ printf("\n");
+ printf("host.nqn=%s\n", table->host.nqn);
+ printf("host.host_id_configured=%d\n", table->host.host_id_configured);
+ printf("host.host_nqn_configured=%d\n", table->host.host_nqn_configured);
+ printf("host.primary=%d\n", table->host.primary);
+
+ for (hfi = table->hfi_list, i = 0; hfi && *hfi; hfi++, i++) {
+ printf("hfi_list[%u]->index=%d\n", i, (*hfi)->index);
+ printf("hfi_list[%u]->transport=%.*s\n", i, (int)sizeof((*hfi)->transport), (*hfi)->transport);
+ printf("hfi_list[%u]->tcp_info.pci_sbdf=%"PRIu32"\n", i, (*hfi)->tcp_info.pci_sbdf);
+ printf("hfi_list[%u]->tcp_info.mac_addr=", i);
+ print_hex((*hfi)->tcp_info.mac_addr, sizeof((*hfi)->tcp_info.mac_addr));
+ printf("\n");
+ printf("hfi_list[%u]->tcp_info.vlan=%"PRIu16"\n", i, (*hfi)->tcp_info.vlan);
+ printf("hfi_list[%u]->tcp_info.ip_origin=%u\n", i, (*hfi)->tcp_info.ip_origin);
+ printf("hfi_list[%u]->tcp_info.ipaddr=%s\n", i, (*hfi)->tcp_info.ipaddr);
+ printf("hfi_list[%u]->tcp_info.subnet_mask_prefix=%u\n", i, (*hfi)->tcp_info.subnet_mask_prefix);
+ printf("hfi_list[%u]->tcp_info.gateway_ipaddr=%s\n", i, (*hfi)->tcp_info.gateway_ipaddr);
+ printf("hfi_list[%u]->tcp_info.route_metric=%"PRIu16"\n", i, (*hfi)->tcp_info.route_metric);
+ printf("hfi_list[%u]->tcp_info.primary_dns_ipaddr=%s\n", i, (*hfi)->tcp_info.primary_dns_ipaddr);
+ printf("hfi_list[%u]->tcp_info.secondary_dns_ipaddr=%s\n", i, (*hfi)->tcp_info.secondary_dns_ipaddr);
+ printf("hfi_list[%u]->tcp_info.dhcp_server_ipaddr=%s\n", i, (*hfi)->tcp_info.dhcp_server_ipaddr);
+ printf("hfi_list[%u]->tcp_info.host_name=%s\n", i, (*hfi)->tcp_info.host_name);
+ printf("hfi_list[%u]->tcp_info.this_hfi_is_default_route=%d\n", i, (*hfi)->tcp_info.this_hfi_is_default_route);
+ printf("hfi_list[%u]->tcp_info.dhcp_override=%d\n", i, (*hfi)->tcp_info.dhcp_override);
+ }
+
+ for (sec = table->security_list, i = 0; sec && *sec; sec++, i++) {
+ printf("security_list[%u]->index=%d\n", i, (*sec)->index);
+ }
+
+ for (disc = table->discovery_list, i = 0; disc && *disc; disc++, i++) {
+ printf("discovery_list[%u]->index=%d\n", i, (*disc)->index);
+ if ((*disc)->security)
+ printf("discovery_list[%u]->security->index=%d\n", i, (*disc)->security->index);
+ if ((*disc)->hfi)
+ printf("discovery_list[%u]->hfi->index=%d\n", i, (*disc)->hfi->index);
+ printf("discovery_list[%u]->uri=%s\n", i, (*disc)->uri);
+ printf("discovery_list[%u]->nqn=%s\n", i, (*disc)->nqn);
+ }
+
+ for (ssns = table->subsystem_ns_list, i = 0; ssns && *ssns; ssns++, i++) {
+ printf("subsystem_ns_list[%u]->index=%d\n", i, (*ssns)->index);
+ if ((*ssns)->discovery)
+ printf("subsystem_ns_list[%u]->discovery->index=%d\n", i, (*ssns)->discovery->index);
+ if ((*ssns)->security)
+ printf("subsystem_ns_list[%u]->security->index=%d\n", i, (*ssns)->security->index);
+ printf("subsystem_ns_list[%u]->num_hfis=%d\n", i, (*ssns)->num_hfis);
+ for (hfi = (*ssns)->hfis, j = 0; hfi && *hfi; hfi++, j++)
+ printf("subsystem_ns_list[%u]->hfis[%u]->index=%d\n", i, j, (*hfi)->index);
+ printf("subsystem_ns_list[%u]->transport=%s\n", i, (*ssns)->transport);
+ printf("subsystem_ns_list[%u]->traddr=%s\n", i, (*ssns)->traddr);
+ printf("subsystem_ns_list[%u]->trsvcid=%s\n", i, (*ssns)->trsvcid);
+ printf("subsystem_ns_list[%u]->subsys_port_id=%"PRIu16"\n", i, (*ssns)->subsys_port_id);
+ printf("subsystem_ns_list[%u]->nsid=%"PRIu32"\n", i, (*ssns)->nsid);
+ printf("subsystem_ns_list[%u]->nid_type=%d\n", i, (*ssns)->nid_type);
+ printf("subsystem_ns_list[%u]->nid=", i);
+ print_hex((*ssns)->nid, 16);
+ printf("\n");
+ printf("subsystem_ns_list[%u]->subsys_nqn=%s\n", i, (*ssns)->subsys_nqn);
+ printf("subsystem_ns_list[%u]->pdu_header_digest_required=%d\n", i, (*ssns)->pdu_header_digest_required);
+ printf("subsystem_ns_list[%u]->data_digest_required=%d\n", i, (*ssns)->data_digest_required);
+ printf("subsystem_ns_list[%u]->controller_id=%d\n", i, (*ssns)->controller_id);
+ printf("subsystem_ns_list[%u]->asqsz=%d\n", i, (*ssns)->asqsz);
+ printf("subsystem_ns_list[%u]->dhcp_root_path_string=%s\n", i, (*ssns)->dhcp_root_path_string);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct nbft_info *table = NULL;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s TABLE\n", argv[0]);
+ return 1;
+ }
+
+ if (nvme_nbft_read(&table, argv[1]) != 0) {
+ fprintf(stderr, "Error parsing the NBFT table %s: %m\n",
+ argv[1]);
+ return 2;
+ }
+
+ print_nbft(table);
+
+ nvme_nbft_free(table);
+ return 0;
+}
diff --git a/test/nbft/tables/NBFT-Dell.PowerEdge.R660-fw1.5.5-single b/test/nbft/tables/NBFT-Dell.PowerEdge.R660-fw1.5.5-single
new file mode 100644
index 0000000..845a8e2
--- /dev/null
+++ b/test/nbft/tables/NBFT-Dell.PowerEdge.R660-fw1.5.5-single
Binary files differ
diff --git a/test/nbft/tables/NBFT-Dell.PowerEdge.R760 b/test/nbft/tables/NBFT-Dell.PowerEdge.R760
new file mode 120000
index 0000000..2e5c8dc
--- /dev/null
+++ b/test/nbft/tables/NBFT-Dell.PowerEdge.R760
@@ -0,0 +1 @@
+../../../libnvme/tests/NBFT \ No newline at end of file
diff --git a/test/nbft/tables/NBFT-auto-ipv6 b/test/nbft/tables/NBFT-auto-ipv6
new file mode 100644
index 0000000..64457d7
--- /dev/null
+++ b/test/nbft/tables/NBFT-auto-ipv6
Binary files differ
diff --git a/test/nbft/tables/NBFT-dhcp-ipv4 b/test/nbft/tables/NBFT-dhcp-ipv4
new file mode 100644
index 0000000..1af159d
--- /dev/null
+++ b/test/nbft/tables/NBFT-dhcp-ipv4
Binary files differ
diff --git a/test/nbft/tables/NBFT-dhcp-ipv6 b/test/nbft/tables/NBFT-dhcp-ipv6
new file mode 100644
index 0000000..20715ee
--- /dev/null
+++ b/test/nbft/tables/NBFT-dhcp-ipv6
Binary files differ
diff --git a/test/nbft/tables/NBFT-rhpoc b/test/nbft/tables/NBFT-rhpoc
new file mode 100644
index 0000000..5d0a6cc
--- /dev/null
+++ b/test/nbft/tables/NBFT-rhpoc
Binary files differ
diff --git a/test/nbft/tables/NBFT-static-ipv4 b/test/nbft/tables/NBFT-static-ipv4
new file mode 100644
index 0000000..bf3f840
--- /dev/null
+++ b/test/nbft/tables/NBFT-static-ipv4
Binary files differ
diff --git a/test/nbft/tables/NBFT-static-ipv4-discovery b/test/nbft/tables/NBFT-static-ipv4-discovery
new file mode 100644
index 0000000..7ebb40e
--- /dev/null
+++ b/test/nbft/tables/NBFT-static-ipv4-discovery
Binary files differ
diff --git a/test/nbft/tables/NBFT-static-ipv6 b/test/nbft/tables/NBFT-static-ipv6
new file mode 100644
index 0000000..07b09cf
--- /dev/null
+++ b/test/nbft/tables/NBFT-static-ipv6
Binary files differ
diff --git a/test/nbft/tables_bad/NBFT-bad-oldspec b/test/nbft/tables_bad/NBFT-bad-oldspec
new file mode 100644
index 0000000..e09d6ad
--- /dev/null
+++ b/test/nbft/tables_bad/NBFT-bad-oldspec
Binary files differ
diff --git a/test/nbft/tables_bad/NBFT-random-noise b/test/nbft/tables_bad/NBFT-random-noise
new file mode 100644
index 0000000..296bcfe
--- /dev/null
+++ b/test/nbft/tables_bad/NBFT-random-noise
Binary files differ
diff --git a/test/register.c b/test/register.c
new file mode 100644
index 0000000..8a41628
--- /dev/null
+++ b/test/register.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors: Keith Busch <keith.busch@wdc.com>
+ */
+
+/**
+ * Prints the values of the nvme register map. Use the nvme controller resource
+ * for your pci device found in /sys/class/nvme/nvmeX/device/resource0
+ */
+
+#define __SANE_USERSPACE_TYPES__
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libnvme.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+
+#include <ccan/endian/endian.h>
+
+static inline uint32_t nvme_mmio_read32(volatile void *addr)
+{
+ uint32_t *p = (__le32 *)addr;
+
+ return le32_to_cpu(*p);
+}
+
+static inline uint64_t nvme_mmio_read64(volatile void *addr)
+{
+ volatile __u32 *p = (__u32 *)addr;
+ uint32_t low, high;
+
+ low = nvme_mmio_read32(p);
+ high = nvme_mmio_read32(p + 1);
+
+ return low + ((uint64_t)high << 32);
+}
+
+void nvme_print_registers(void *regs)
+{
+ __u64 cap = nvme_mmio_read64(regs + NVME_REG_CAP);
+ __u32 vs = nvme_mmio_read32(regs + NVME_REG_VS);
+ __u32 intms = nvme_mmio_read32(regs + NVME_REG_INTMS);
+ __u32 intmc = nvme_mmio_read32(regs + NVME_REG_INTMC);
+ __u32 cc = nvme_mmio_read32(regs + NVME_REG_CC);
+ __u32 csts = nvme_mmio_read32(regs + NVME_REG_CSTS);
+ __u32 nssr = nvme_mmio_read32(regs + NVME_REG_NSSR);
+ __u32 aqa = nvme_mmio_read32(regs + NVME_REG_AQA);
+ __u64 asq = nvme_mmio_read64(regs + NVME_REG_ASQ);
+ __u64 acq = nvme_mmio_read64(regs + NVME_REG_ACQ);
+ __u32 cmbloc = nvme_mmio_read32(regs + NVME_REG_CMBLOC);
+ __u32 cmbsz = nvme_mmio_read32(regs + NVME_REG_CMBSZ);
+ __u32 bpinfo = nvme_mmio_read32(regs + NVME_REG_BPINFO);
+ __u32 bprsel = nvme_mmio_read32(regs + NVME_REG_BPRSEL);
+ __u64 bpmbl = nvme_mmio_read64(regs + NVME_REG_BPMBL);
+ __u64 cmbmsc = nvme_mmio_read64(regs + NVME_REG_CMBMSC);
+ __u32 cmbsts = nvme_mmio_read32(regs + NVME_REG_CMBSTS);
+ __u32 pmrcap = nvme_mmio_read32(regs + NVME_REG_PMRCAP);
+ __u32 pmrctl = nvme_mmio_read32(regs + NVME_REG_PMRCTL);
+ __u32 pmrsts = nvme_mmio_read32(regs + NVME_REG_PMRSTS);
+ __u32 pmrebs = nvme_mmio_read32(regs + NVME_REG_PMREBS);
+ __u32 pmrswtp = nvme_mmio_read32(regs + NVME_REG_PMRSWTP);
+ __u64 pmrmsc = nvme_mmio_read32(regs + NVME_REG_PMRMSCL) |
+ (__u64)nvme_mmio_read64(regs + NVME_REG_PMRMSCU) << 32;
+
+ printf("%-10s : %llx\n", "CAP", cap);
+ printf(" %-8s : %llx\n", "MQES", NVME_CAP_MQES(cap));
+ printf(" %-8s : %llx\n", "CQRS", NVME_CAP_CQR(cap));
+ printf(" %-8s : %llx\n", "AMS", NVME_CAP_AMS(cap));
+ printf(" %-8s : %llx\n", "TO", NVME_CAP_TO(cap));
+ printf(" %-8s : %llx\n", "DSTRD", NVME_CAP_DSTRD(cap));
+ printf(" %-8s : %llx\n", "NSSRC", NVME_CAP_NSSRC(cap));
+ printf(" %-8s : %llx\n", "CSS", NVME_CAP_CSS(cap));
+ printf(" %-8s : %llx\n", "BPS", NVME_CAP_BPS(cap));
+ printf(" %-8s : %llx\n", "MPSMIN", NVME_CAP_MPSMIN(cap));
+ printf(" %-8s : %llx\n", "MPSMAX", NVME_CAP_MPSMAX(cap));
+ printf(" %-8s : %llx\n", "CMBS", NVME_CAP_CMBS(cap));
+ printf(" %-8s : %llx\n", "PMRS", NVME_CAP_PMRS(cap));
+
+ printf("%-10s : %x\n", "VS", vs);
+ printf(" %-8s : %x\n", "MJR", NVME_VS_TER(vs));
+ printf(" %-8s : %x\n", "MNR", NVME_VS_MNR(vs));
+ printf(" %-8s : %x\n", "TER", NVME_VS_MJR(vs));
+
+ printf("%-10s : %x\n", "INTMS", intms);
+ printf("%-10s : %x\n", "INTMC", intmc);
+
+ printf("%-10s : %x\n", "CC", cc);
+ printf(" %-8s : %x\n", "EN", NVME_CC_EN(cc));
+ printf(" %-8s : %x\n", "CSS", NVME_CC_CSS(cc));
+ printf(" %-8s : %x\n", "MPS", NVME_CC_MPS(cc));
+ printf(" %-8s : %x\n", "AMS", NVME_CC_AMS(cc));
+ printf(" %-8s : %x\n", "SHN", NVME_CC_SHN(cc));
+ printf(" %-8s : %x\n", "IOSQES", NVME_CC_IOSQES(cc));
+ printf(" %-8s : %x\n", "IOCQES", NVME_CC_IOCQES(cc));
+
+ printf("%-10s : %x\n", "CSTS", csts);
+ printf(" %-8s : %x\n", "RDY", NVME_CSTS_RDY(csts));
+ printf(" %-8s : %x\n", "CFS", NVME_CSTS_CFS(csts));
+ printf(" %-8s : %x\n", "SHST", NVME_CSTS_SHST(csts));
+ printf(" %-8s : %x\n", "NSSRO", NVME_CSTS_NSSRO(csts));
+ printf(" %-8s : %x\n", "PP", NVME_CSTS_PP(csts));
+
+ printf("%-10s : %x\n", "NSSR", nssr);
+
+ printf("%-10s : %x\n", "AQA", aqa);
+ printf(" %-8s : %x\n", "ASQS", NVME_AQA_ASQS(aqa));
+ printf(" %-8s : %x\n", "ACQS", NVME_AQA_ACQS(aqa));
+
+ printf("%-10s : %llx\n", "ASQ", asq);
+ printf("%-10s : %llx\n", "ACQ", acq);
+
+ printf("%-10s : %x\n", "CMBLOC", cmbloc);
+ printf(" %-8s : %x\n", "BIR", NVME_CMBLOC_BIR(cmbloc));
+ printf(" %-8s : %x\n", "CQMMS", NVME_CMBLOC_CQMMS(cmbloc));
+ printf(" %-8s : %x\n", "CQPDS", NVME_CMBLOC_CQPDS(cmbloc));
+ printf(" %-8s : %x\n", "CDPLMS", NVME_CMBLOC_CDPLMS(cmbloc));
+ printf(" %-8s : %x\n", "CDPCILS", NVME_CMBLOC_CDPCILS(cmbloc));
+ printf(" %-8s : %x\n", "CDMMMS", NVME_CMBLOC_CDMMMS(cmbloc));
+ printf(" %-8s : %x\n", "CQDA", NVME_CMBLOC_CQDA(cmbloc));
+ printf(" %-8s : %x\n", "OFST", NVME_CMBLOC_OFST(cmbloc));
+
+ printf("%-10s : %x\n", "CMBSZ", cmbsz);
+ printf(" %-8s : %x\n", "SQS", NVME_CMBSZ_SQS(cmbsz));
+ printf(" %-8s : %x\n", "CQS", NVME_CMBSZ_CQS(cmbsz));
+ printf(" %-8s : %x\n", "LISTS", NVME_CMBSZ_LISTS(cmbsz));
+ printf(" %-8s : %x\n", "RDS", NVME_CMBSZ_RDS(cmbsz));
+ printf(" %-8s : %x\n", "WDS", NVME_CMBSZ_WDS(cmbsz));
+ printf(" %-8s : %x\n", "SZU", NVME_CMBSZ_SZU(cmbsz));
+ printf(" %-8s : %x\n", "SZ", NVME_CMBSZ_SZ(cmbsz));
+ printf(" %-8s : %llx\n", "bytes", nvme_cmb_size(cmbsz));
+
+ printf("%-10s : %x\n", "BPINFO", bpinfo);
+ printf(" %-8s : %x\n", "BPSZ", NVME_BPINFO_BPSZ(bpinfo));
+ printf(" %-8s : %x\n", "BRS", NVME_BPINFO_BRS(bpinfo));
+ printf(" %-8s : %x\n", "ABPID", NVME_BPINFO_ABPID(bpinfo));
+
+ printf("%-10s : %x\n", "BPRSEL", bprsel);
+ printf(" %-8s : %x\n", "BPRSZ", NVME_BPRSEL_BPRSZ(bprsel));
+ printf(" %-8s : %x\n", "BPROF", NVME_BPRSEL_BPROF(bprsel));
+ printf(" %-8s : %x\n", "BPID", NVME_BPRSEL_BPID(bprsel));
+
+ printf("%-10s : %llx\n", "BPMBL", bpmbl);
+
+ printf("%-10s : %llx\n", "CMBMSC", cmbmsc);
+ printf(" %-8s : %llx\n", "CRE", NVME_CMBMSC_CRE(cmbmsc));
+ printf(" %-8s : %llx\n", "CMSE", NVME_CMBMSC_CMSE(cmbmsc));
+ printf(" %-8s : %llx\n", "CBA", NVME_CMBMSC_CBA(cmbmsc));
+
+ printf("%-10s : %x\n", "CMBSTS", cmbsts);
+ printf(" %-8s : %x\n", "CBAI", NVME_CMBSTS_CBAI(cmbsts));
+
+ printf("%-10s : %x\n", "PMRCAP", pmrcap);
+ printf(" %-8s : %x\n", "RDS", NVME_PMRCAP_RDS(pmrcap));
+ printf(" %-8s : %x\n", "WDS", NVME_PMRCAP_WDS(pmrcap));
+ printf(" %-8s : %x\n", "BIR", NVME_PMRCAP_BIR(pmrcap));
+ printf(" %-8s : %x\n", "PMRTU", NVME_PMRCAP_PMRTU(pmrcap));
+ printf(" %-8s : %x\n", "PMRWMB", NVME_PMRCAP_PMRWMB(pmrcap));
+ printf(" %-8s : %x\n", "PMRTO", NVME_PMRCAP_PMRTO(pmrcap));
+ printf(" %-8s : %x\n", "CMSS", NVME_PMRCAP_CMSS(pmrcap));
+
+ printf("%-10s : %x\n", "PMRCTL", pmrctl);
+ printf(" %-8s : %x\n", "EN", NVME_PMRCTL_EN(pmrctl));
+
+ printf("%-10s : %x\n", "PMRSTS", pmrsts);
+ printf(" %-8s : %x\n", "ERR", NVME_PMRSTS_ERR(pmrsts));
+ printf(" %-8s : %x\n", "NRDY", NVME_PMRSTS_NRDY(pmrsts));
+ printf(" %-8s : %x\n", "HSTS", NVME_PMRSTS_HSTS(pmrsts));
+ printf(" %-8s : %x\n", "CBAI", NVME_PMRSTS_CBAI(pmrsts));
+
+ printf("%-10s : %x\n", "PMREBS", pmrebs);
+ printf(" %-8s : %x\n", "PMRSZU", NVME_PMREBS_PMRSZU(pmrebs));
+ printf(" %-8s : %x\n", "RBB", NVME_PMREBS_RBB(pmrebs));
+ printf(" %-8s : %x\n", "PMRWBZ", NVME_PMREBS_PMRWBZ(pmrebs));
+ printf(" %-8s : %llx\n", "bytes", nvme_pmr_size(pmrebs));
+
+ printf("%-10s : %x\n", "PMRSWTP", pmrswtp);
+ printf(" %-8s : %x\n", "PMRSWTU", NVME_PMRSWTP_PMRSWTU(pmrswtp));
+ printf(" %-8s : %x\n", "PMRSWTV", NVME_PMRSWTP_PMRSWTV(pmrswtp));
+ printf(" %-8s : %llx\n", "tput", nvme_pmr_throughput(pmrswtp));
+
+ printf("%-10s : %llx\n", "PMRMSC", pmrmsc);
+ printf(" %-8s : %llx\n", "CMSE", NVME_PMRMSC_CMSE(pmrmsc));
+ printf(" %-8s : %llx\n", "CBA", NVME_PMRMSC_CBA(pmrmsc));
+}
+
+int main(int argc, char **argv)
+{
+ int ret, fd;
+ char *path;
+ void *regs;
+
+ if (argc != 2) {
+ fprintf(stderr, "%s nvme<X>\n", argv[0]);
+ return 1;
+ }
+
+ ret = asprintf(&path, "/sys/class/nvme/%s/device/resource0", argv[1]);
+ if (ret < 0)
+ return 0;
+
+ printf("open %s\n", path);
+ fd = open(path, O_RDONLY | O_SYNC);
+ if (fd < 0) {
+ fprintf(stderr, "failed to open %s\n", path);
+ free(path);
+ return 1;
+ }
+
+ regs = mmap(NULL, getpagesize(), PROT_READ, MAP_SHARED, fd, 0);
+ if (regs == MAP_FAILED) {
+ fprintf(stderr, "failed to map device BAR\n");
+ fprintf(stderr, "did your kernel enable CONFIG_IO_STRICT_DEVMEM?\n");
+ free(path);
+ close(fd);
+ return 1;
+ }
+
+ nvme_print_registers(regs);
+ munmap(regs, getpagesize());
+ free(path);
+ close(fd);
+
+ return 0;
+}
+
diff --git a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out b/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out
new file mode 100644
index 0000000..1cb6de4
--- /dev/null
+++ b/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.out
@@ -0,0 +1,32 @@
+{
+ "hosts":[
+ {
+ "hostnqn":"nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6",
+ "hostid":"ce4fee3e-c02c-11ee-8442-830d068a36c6",
+ "subsystems":[
+ {
+ "name":"nvme-subsys1",
+ "nqn":"nqn.2019-08.org.qemu:nvme-0",
+ "controllers":[
+ {
+ "name":"nvme1",
+ "transport":"pcie",
+ "traddr":"0000:00:05.0"
+ }
+ ]
+ },
+ {
+ "name":"nvme-subsys0",
+ "nqn":"nqn.2019-08.org.qemu:subsys1",
+ "controllers":[
+ {
+ "name":"nvme0",
+ "transport":"pcie",
+ "traddr":"0000:0f:00.0"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz b/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz
new file mode 100644
index 0000000..ee11fde
--- /dev/null
+++ b/test/sysfs/data/nvme-sysfs-tw-carbon-6.8.0-rc1+.tar.xz
Binary files differ
diff --git a/test/sysfs/meson.build b/test/sysfs/meson.build
new file mode 100644
index 0000000..c004fc0
--- /dev/null
+++ b/test/sysfs/meson.build
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of libnvme.
+# Copyright (c) 2024 SUSE LLC.
+#
+# Authors: Daniel Wagner <dwagner@suse.de>
+
+
+sysfs = executable(
+ 'test-sysfs',
+ ['sysfs.c'],
+ dependencies: libnvme_dep,
+ include_directories: [incdir, internal_incdir]
+)
+
+sysfs_files= [
+ 'nvme-sysfs-tw-carbon-6.8.0-rc1+'
+]
+
+setup = find_program('setup.sh')
+
+foreach t_file : sysfs_files
+ r = run_command(setup, files('data'/t_file + '.tar.xz'), meson.current_build_dir(), check: true)
+ i = r.stdout().strip()
+ e0 = 'LIBNVME_SYSFS_PATH=' + i
+ e1 = 'LIBNVME_HOSTNQN=nqn.2014-08.org.nvmexpress:uuid:ce4fee3e-c02c-11ee-8442-830d068a36c6'
+ e2 = 'LIBNVME_HOSTID=ce4fee3e-c02c-11ee-8442-830d068a36c6'
+ test('sysfs', sysfs, args : [ i, t_file + '.out', files('data'/t_file + '.out') ], env : [ e0, e1, e2 ])
+endforeach
diff --git a/test/sysfs/setup.sh b/test/sysfs/setup.sh
new file mode 100755
index 0000000..3437d4e
--- /dev/null
+++ b/test/sysfs/setup.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+TARFILE=$1
+BASEDIR=$2
+TESTDIR="$BASEDIR/$(basename -s .tar.xz ${TARFILE})"
+
+mkdir -p "${TESTDIR}"
+tar -x -f "${TARFILE}" -C "${TESTDIR}" || exit 1
+
+echo "${TESTDIR}"
diff --git a/test/sysfs/sysfs.c b/test/sysfs/sysfs.c
new file mode 100644
index 0000000..c2df178
--- /dev/null
+++ b/test/sysfs/sysfs.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2024 Daniel Wagner, SUSE LLC
+ */
+
+#include "nvme/tree.h"
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <ccan/array_size/array_size.h>
+
+#include <libnvme.h>
+#include <nvme/private.h>
+
+static bool test_sysfs(const char *path, const char *filename)
+{
+ FILE *f;
+ nvme_root_t r;
+ int err;
+
+ f = fopen(filename, "w");
+ if (!f)
+ return false;
+
+ r = nvme_create_root(f, LOG_ERR);
+ assert(r);
+
+ err = nvme_scan_topology(r, NULL, NULL);
+ if (!err)
+ nvme_dump_tree(r);
+ fprintf(f, "\n");
+
+ nvme_free_tree(r);
+ fclose(f);
+
+ return err == 0;
+}
+
+static bool compare_content(const char *filename1, const char *filename2)
+{
+ FILE *f1, *f2;
+ char c1, c2;
+ bool pass = false;
+
+ f1 = fopen(filename1, "r");
+ if (!f1)
+ return false;
+
+ f2 = fopen(filename2, "r");
+ if (!f2) {
+ fclose(f1);
+ return false;
+ }
+
+ do {
+ c1 = getc(f1);
+ c2 = getc(f2);
+ if (c1 != c2)
+ goto out;
+ } while (c1 != EOF || c2 != EOF);
+
+ if (c1 == c2)
+ pass = true;
+out:
+ fclose(f1);
+ fclose(f2);
+
+ return pass;
+}
+
+int main(int argc, char *argv[])
+{
+ bool pass = true;
+
+ if (argc < 4) {
+ fprintf(stderr, "usage: test-sysfs SYSFS_DIR OUTPUT_FILE COMPARE_FILE\n");
+ return EXIT_FAILURE;
+ }
+
+ pass &= test_sysfs(argv[1], argv[2]);
+ pass &= compare_content(argv[2], argv[3]);
+
+ exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/test/test-util.c b/test/test-util.c
new file mode 100644
index 0000000..88a3f42
--- /dev/null
+++ b/test/test-util.c
@@ -0,0 +1,121 @@
+/**
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ This file is part of libnvme.
+ Copyright (c) 2023 Dell Inc.
+
+ Authors: Martin Belanger <Martin.Belanger@dell.com>
+*/
+
+/**
+ * In this file we test private and public functions found in
+ * "src/nvme/util.c". Note that the source files are included
+ * directly because the private functions are not available from
+ * the libnvme.so.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <netdb.h>
+#include <string.h>
+
+#include "nvme/log.c" /* to resolve __nvme_msg() */
+#include "nvme/util.c"
+
+static size_t safe_strlen(const char *p) {
+ return p ? strlen(p) : strlen("null");
+}
+
+static bool test_nvme_get_version(enum nvme_version type, const char * exp_str) {
+ const char * str;
+ str = nvme_get_version(type);
+ return !strcmp(str, exp_str);
+}
+
+static bool test_ipaddrs_eq() {
+ int test_success = true;
+ static const char *x = "1.1.1.1";
+ struct {
+ const char *a;
+ const char *b;
+ bool exp_result;
+ } addrs[] = {
+ {"192.168.56.101", "192.168.56.101", true},
+ {"2001:0db8:0000:0000:0000:ff00:0042:8329", "2001:0db8::ff00:0042:8329", true},
+ {NULL, NULL, true},
+ {x, x, true},
+ {"::ffff:192.168.56.101", "::ffff:192.168.56.101", true},
+ {"::ffff:192.168.56.101", "192.168.56.101", true},
+ {"192.168.56.101", "::ffff:192.168.56.101", true},
+ {"::ffff:192.168.56.222", "192.168.56.101", false},
+ {"192.168.56.101", "::ffff:192.168.56.222", false},
+ {"1.2.3.4", "192.168.56.101", false},
+ {"!@#$", "192.168.56.101", false},
+ {"192.168.56.101", "!@#$", false},
+ {"2001:0db8:0001:0000:0000:ff00:0042:8329", "2001:0db8::ff00:0042:8329", false},
+ {"2001:0db8:0001:0000:0000:ff00:0042:8329", NULL, false},
+ };
+
+ size_t i;
+ size_t n = sizeof(addrs) / sizeof(addrs[0]);
+ size_t longest_a = 0, longest_b = 0;
+
+ for (i = 0; i < n; i++) {
+ size_t l;
+ l = safe_strlen(addrs[i].a);
+ if (l > longest_a) longest_a = l;
+ l = safe_strlen(addrs[i].b);
+ if (l > longest_b) longest_b = l;
+ }
+
+ for (i = 0; i < n; i++) {
+ bool result = nvme_ipaddrs_eq(addrs[i].a, addrs[i].b);
+ bool pass = result == addrs[i].exp_result;
+ int pad_a = longest_a - safe_strlen(addrs[i].a);
+ int pad_b = longest_b - safe_strlen(addrs[i].b);
+ printf("%s %*.*s %s %*.*s -> %-10s %s\n",
+ addrs[i].a ? addrs[i].a : "null",
+ pad_a, pad_a, "",
+ addrs[i].b ? addrs[i].b : "null",
+ pad_b, pad_b, "",
+ result ? "equal/same" : "different",
+ pass ? "[PASS]" : "[FAIL]");
+
+ if (!pass)
+ test_success = false;
+ }
+
+ return test_success;
+}
+
+int main(int argc, char *argv[]) {
+ int exit_val = EXIT_SUCCESS;
+ bool pass;
+
+ printf("\n------------------------------------------------------------------------------\n");
+ pass = test_nvme_get_version(NVME_VERSION_PROJECT, PROJECT_VERSION);
+ printf("nvme_get_version(NVME_VERSION_PROJECT) %s\n", pass ? "[PASS]" : "[FAIL]");
+ if (!pass)
+ exit_val = EXIT_FAILURE;
+
+ printf("\n------------------------------------------------------------------------------\n");
+ pass = test_nvme_get_version(NVME_VERSION_GIT, GIT_VERSION);
+ printf("nvme_get_version(NVME_VERSION_GIT) %s\n", pass ? "[PASS]" : "[FAIL]");
+ if (!pass)
+ exit_val = EXIT_FAILURE;
+
+ printf("\n------------------------------------------------------------------------------\n");
+ pass = test_nvme_get_version(-1, "n/a");
+ printf("nvme_get_version(-1) %s\n", pass ? "[PASS]" : "[FAIL]");
+ if (!pass)
+ exit_val = EXIT_FAILURE;
+
+ printf("\n------------------------------------------------------------------------------\n");
+ pass = test_ipaddrs_eq();
+ printf("nvme_ipaddrs_eq() %s", pass ? "[PASS]" : "[FAIL]");
+ if (!pass)
+ exit_val = EXIT_FAILURE;
+
+ exit(exit_val);
+}
diff --git a/test/test.c b/test/test.c
new file mode 100644
index 0000000..23036bb
--- /dev/null
+++ b/test/test.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors: Keith Busch <keith.busch@wdc.com>
+ */
+
+/**
+ * Basic libnvme test: uses scan filters, single controllers, and many admin
+ * command APIs for identifications, logs, and features. No verification for
+ * specific values are performed: the test will only report which commands
+ * executed were completed successfully or with an error. User inspection of
+ * the output woould be required to know if everything is working when the
+ * program exists successfully; an ungraceful exit means a bug exists
+ * somewhere.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <libnvme.h>
+
+#include <ccan/endian/endian.h>
+
+static bool nvme_match_subsysnqn_filter(nvme_subsystem_t s,
+ nvme_ctrl_t c, nvme_ns_t ns, void *f_args)
+{
+ char *nqn_match = f_args;
+
+ if (s)
+ return strcmp(nvme_subsystem_get_nqn(s), nqn_match) == 0;
+ return true;
+}
+
+static int test_ctrl(nvme_ctrl_t c)
+{
+ static __u8 buf[0x1000];
+
+ enum nvme_get_features_sel sel = NVME_GET_FEATURES_SEL_CURRENT;
+ int ret, temp, fd = nvme_ctrl_get_fd(c);
+ struct nvme_error_log_page error[64];
+ struct nvme_smart_log smart = { 0 };
+ struct nvme_firmware_slot fw = { 0 };
+ struct nvme_ns_list ns_list = { 0 };
+ struct nvme_cmd_effects_log cfx = { 0 };
+ struct nvme_self_test_log st = { 0 };
+ struct nvme_telemetry_log *telem = (void *)buf;
+ struct nvme_endurance_group_log eglog = { 0 };
+ struct nvme_ana_group_desc *analog = (void *)buf;
+ struct nvme_resv_notification_log resvnotify = { 0 };
+ struct nvme_sanitize_log_page sanlog = { 0 };
+ struct nvme_id_uuid_list uuid = { 0 };
+ struct nvme_id_ns_granularity_list gran = { 0 };
+ struct nvme_secondary_ctrl_list sec = { 0 };
+ struct nvme_primary_ctrl_cap prim = { 0 };
+ struct nvme_ctrl_list ctrlist = { 0 };
+ struct nvme_id_ctrl id = { 0 };
+
+ __u32 result;
+
+ ret = nvme_ctrl_identify(c, &id);
+ if (ret) {
+ printf("ERROR: no identify for:%s\n", nvme_ctrl_get_name(c));
+ return ret;
+ }
+ else {
+ printf("PASSED: Identify controller\n");
+ }
+
+ ret = nvme_get_log_smart(fd, NVME_NSID_ALL, true, &smart);
+ if (ret) {
+ printf("ERROR: no smart log for:%s %#x\n", nvme_ctrl_get_name(c), ret);
+ return ret;
+ }
+ else {
+ printf("PASSED: smart log\n");
+ }
+
+ temp = ((smart.temperature[1] << 8) | smart.temperature[0]) - 273;
+ printf("Controller:%s\n", nvme_ctrl_get_name(c));
+ printf("\nIdentify:\n");
+ printf(" vid:%#04x\n", le16_to_cpu(id.vid));
+ printf(" ssvid:%#04x\n", le16_to_cpu(id.ssvid));
+ printf(" oacs:%#x\n", id.oacs);
+ printf(" lpa:%#x\n", id.lpa);
+ printf(" sn:%-.20s\n", id.sn);
+ printf(" model:%-.40s\n", id.mn);
+
+ ret = nvme_identify_allocated_ns_list(fd, 0, &ns_list);
+ if (!ret)
+ printf(" PASSED: Allocated NS List\n");
+ else
+ printf(" ERROR: Allocated NS List:%x\n", ret);
+ ret = nvme_identify_active_ns_list(fd, 0, &ns_list);
+ if (!ret)
+ printf(" PASSED: Active NS List\n");
+ else
+ printf(" ERROR: Active NS List:%x\n", ret);
+ ret = nvme_identify_ctrl_list(fd, 0, &ctrlist);
+ if (!ret)
+ printf(" PASSED: Ctrl List\n");
+ else
+ printf(" ERROR: CtrlList:%x\n", ret);
+ ret = nvme_identify_nsid_ctrl_list(fd, 1, 0, &ctrlist);
+ if (!ret)
+ printf(" PASSED: NSID Ctrl List\n");
+ else
+ printf(" ERROR: NSID CtrlList:%x\n", ret);
+ ret = nvme_identify_primary_ctrl(fd, 0, &prim);
+ if (!ret)
+ printf(" PASSED: Identify Primary\n");
+ else
+ printf(" ERROR: Identify Primary:%x\n", ret);
+ ret = nvme_identify_secondary_ctrl_list(fd, 0, &sec);
+ if (!ret)
+ printf(" PASSED: Identify Secondary\n");
+ else
+ printf(" ERROR: Identify Secondary:%x\n", ret);
+ ret = nvme_identify_ns_granularity(fd, &gran);
+ if (!ret)
+ printf(" PASSED: Identify NS granularity\n");
+ else
+ printf(" ERROR: Identify NS granularity:%x\n", ret);
+ ret = nvme_identify_uuid(fd, &uuid);
+ if (!ret)
+ printf(" PASSED: Identify UUID List\n");
+ else
+ printf(" ERROR: Identify UUID List:%x\n", ret);
+
+ printf("\nLogs\n");
+ printf(" SMART: Current temperature:%d percent used:%d%%\n", temp,
+ smart.percent_used);
+ ret = nvme_get_log_sanitize(fd, true, &sanlog);
+ if (!ret)
+ printf(" Sanitize Log:\n");
+ else
+ printf(" ERROR: Sanitize Log:%x\n", ret);
+ ret = nvme_get_log_reservation(fd, true, &resvnotify);
+ if (!ret)
+ printf(" Reservation Log\n");
+ else
+ printf(" ERROR: Reservation Log:%x\n", ret);
+ ret = nvme_get_log_ana_groups(fd, true, sizeof(buf), analog);
+ if (!ret)
+ printf(" ANA Groups\n");
+ else
+ printf(" ERROR: ANA Groups:%x\n", ret);
+ ret = nvme_get_log_endurance_group(fd, 0, &eglog);
+ if (!ret)
+ printf(" Endurance Group\n");
+ else
+ printf(" ERROR: Endurance Group:%x\n", ret);
+ ret = nvme_get_log_telemetry_ctrl(fd, true, 0, sizeof(buf), telem);
+ if (!ret)
+ printf(" Telemetry Controller\n");
+ else
+ printf(" ERROR: Telemetry Controller:%x\n", ret);
+ ret = nvme_get_log_device_self_test(fd, &st);
+ if (!ret)
+ printf(" Device Self Test\n");
+ else
+ printf(" ERROR: Device Self Test:%x\n", ret);
+ ret = nvme_get_log_cmd_effects(fd, NVME_CSI_NVM, &cfx);
+ if (!ret)
+ printf(" Command Effects\n");
+ else
+ printf(" ERROR: Command Effects:%x\n", ret);
+ ret = nvme_get_log_changed_ns_list(fd, true, &ns_list);
+ if (!ret)
+ printf(" Change NS List\n");
+ else
+ printf(" ERROR: Change NS List:%x\n", ret);
+ ret = nvme_get_log_fw_slot(fd, true, &fw);
+ if (!ret)
+ printf(" FW Slot\n");
+ else
+ printf(" ERROR: FW Slot%x\n", ret);
+ ret = nvme_get_log_error(fd, 64, true, error);
+ if (!ret)
+ printf(" Error Log\n");
+ else
+ printf(" ERROR: Error Log:%x\n", ret);
+ printf("\nFeatures\n");
+ ret = nvme_get_features_arbitration(fd, sel, &result);
+ if (!ret)
+ printf(" Arbitration:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Arbitration:%x\n", ret);
+ ret = nvme_get_features_power_mgmt(fd, sel, &result);
+ if (!ret)
+ printf(" Power Management:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Power Management:%x\n", ret);
+ ret = nvme_get_features_temp_thresh(fd, sel, &result);
+ if (!ret)
+ printf(" Temperature Threshold:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Temperature Threshold:%x\n", ret);
+ ret = nvme_get_features_err_recovery2(fd, sel, 0, &result);
+ if (!ret)
+ printf(" Error Recovery:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Error Recovery:%x\n", ret);
+ ret = nvme_get_features_volatile_wc(fd, sel, &result);
+ if (!ret)
+ printf(" Volatile Write Cache:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Volatile Write Cache:%x\n", ret);
+ ret = nvme_get_features_num_queues(fd, sel, &result);
+ if (!ret)
+ printf(" Number of Queues:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Number of Queues:%x\n", ret);
+ ret = nvme_get_features_irq_coalesce(fd, sel, &result);
+ if (!ret)
+ printf(" IRQ Coalescing:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: IRQ Coalescing:%x\n", ret);
+ ret = nvme_get_features_write_atomic(fd, sel, &result);
+ if (!ret)
+ printf(" Write Atomic:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Write Atomic:%x\n", ret);
+ ret = nvme_get_features_async_event(fd, sel, &result);
+ if (!ret)
+ printf(" Asycn Event Config:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Asycn Event Config:%x\n", ret);
+ ret = nvme_get_features_hctm(fd, sel, &result);
+ if (!ret)
+ printf(" HCTM:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: HCTM:%x\n", ret);
+ ret = nvme_get_features_nopsc(fd, sel, &result);
+ if (!ret)
+ printf(" NOP Power State Config:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: NOP Power State Configrbitration:%x\n", ret);
+ ret = nvme_get_features_rrl(fd, sel, &result);
+ if (!ret)
+ printf(" Read Recover Levels:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Read Recover Levels:%x\n", ret);
+ ret = nvme_get_features_lba_sts_interval(fd, sel, &result);
+ if (!ret)
+ printf(" LBA Status Interval:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: LBA Status Interval:%x\n", ret);
+ ret = nvme_get_features_sanitize(fd, sel, &result);
+ if (!ret)
+ printf(" Sanitize:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: SW Progress Marker:%x\n", ret);
+ ret = nvme_get_features_sw_progress(fd, sel, &result);
+ if (!ret)
+ printf(" SW Progress Marker:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Sanitize:%x\n", ret);
+ ret = nvme_get_features_resv_mask2(fd, sel, 0, &result);
+ if (!ret)
+ printf(" Reservation Mask:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Reservation Mask:%x\n", ret);
+ ret = nvme_get_features_resv_persist2(fd, sel, 0, &result);
+ if (!ret)
+ printf(" Reservation Persistence:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Reservation Persistence:%x\n", ret);
+ return 0;
+}
+
+static int test_namespace(nvme_ns_t n)
+{
+ int ret, nsid = nvme_ns_get_nsid(n), fd = nvme_ns_get_fd(n);
+ struct nvme_id_ns ns = { 0 }, allocated = { 0 };
+ struct nvme_ns_id_desc *descs;
+ __u32 result = 0;
+ __u8 flbas;
+
+ ret = nvme_ns_identify(n, &ns);
+ if (ret)
+ return ret;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbas);
+ printf("%s: nsze:%" PRIu64 " lba size:%d\n",
+ nvme_ns_get_name(n), le64_to_cpu(ns.nsze),
+ 1 << ns.lbaf[flbas].ds);
+
+ ret = nvme_identify_allocated_ns(fd, nsid, &allocated);
+ if (!ret)
+ printf(" Identify allocated ns\n");
+ else
+ printf(" ERROR: Identify allocated ns:%x\n", ret);
+ descs = malloc(NVME_IDENTIFY_DATA_SIZE);
+ if (!descs)
+ return -1;
+
+ ret = nvme_identify_ns_descs(fd, nsid, descs);
+ if (!ret)
+ printf(" Identify NS Descriptors\n");
+ else
+ printf(" ERROR: Identify NS Descriptors:%x\n", ret);
+ free(descs);
+ ret = nvme_get_features_write_protect(fd, nsid,
+ NVME_GET_FEATURES_SEL_CURRENT, &result);
+ if (!ret)
+ printf(" Write Protect:%x\n", result);
+ else if (ret > 0)
+ printf(" ERROR: Write Protect:%x\n", ret);
+ return 0;
+}
+
+static void print_hex(const uint8_t *x, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ printf("%02x", x[i]);
+}
+
+int main(int argc, char **argv)
+{
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ nvme_path_t p;
+ nvme_ns_t n;
+ const char *ctrl = "nvme4";
+ const char *nqn_match = "testnqn";
+
+ printf("Test filter for common loop back target\n");
+ r = nvme_create_root(NULL, DEFAULT_LOGLEVEL);
+ if (!r)
+ return 1;
+ nvme_scan_topology(r, nvme_match_subsysnqn_filter, (void *)nqn_match);
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ printf("%s - NQN=%s\n", nvme_subsystem_get_name(s),
+ nvme_subsystem_get_nqn(s));
+ nvme_subsystem_for_each_ctrl(s, c) {
+ printf(" %s %s %s %s\n", nvme_ctrl_get_name(c),
+ nvme_ctrl_get_transport(c),
+ nvme_ctrl_get_address(c),
+ nvme_ctrl_get_state(c));
+ }
+ }
+ }
+ printf("\n");
+
+ if (argc > 1)
+ ctrl = argv[1];
+
+ printf("Test scan specific controller\n");
+ c = nvme_scan_ctrl(r, ctrl);
+ if (c) {
+ printf("%s %s %s %s\n", nvme_ctrl_get_name(c),
+ nvme_ctrl_get_transport(c),
+ nvme_ctrl_get_address(c),
+ nvme_ctrl_get_state(c));
+ nvme_free_ctrl(c);
+ }
+ printf("\n");
+ nvme_free_tree(r);
+
+ r = nvme_scan(NULL);
+ if (!r)
+ return -1;
+
+ printf("Test walking the topology\n");
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ printf("%s - NQN=%s\n", nvme_subsystem_get_name(s),
+ nvme_subsystem_get_nqn(s));
+ nvme_subsystem_for_each_ctrl(s, c) {
+ printf(" `- %s %s %s %s\n",
+ nvme_ctrl_get_name(c),
+ nvme_ctrl_get_transport(c),
+ nvme_ctrl_get_address(c),
+ nvme_ctrl_get_state(c));
+
+ nvme_ctrl_for_each_ns(c, n) {
+ char uuid_str[NVME_UUID_LEN_STRING];
+ unsigned char uuid[NVME_UUID_LEN];
+ printf(" `- %s lba size:%d lba max:%" PRIu64 "\n",
+ nvme_ns_get_name(n),
+ nvme_ns_get_lba_size(n),
+ nvme_ns_get_lba_count(n));
+ printf(" eui:");
+ print_hex(nvme_ns_get_eui64(n), 8);
+ printf(" nguid:");
+ print_hex(nvme_ns_get_nguid(n), 16);
+ nvme_ns_get_uuid(n, uuid);
+ nvme_uuid_to_string(uuid, uuid_str);
+ printf(" uuid:%s csi:%d\n", uuid_str,
+ nvme_ns_get_csi(n));
+ }
+
+ nvme_ctrl_for_each_path(c, p)
+ printf(" `- %s %s\n",
+ nvme_path_get_name(p),
+ nvme_path_get_ana_state(p));
+ }
+
+ nvme_subsystem_for_each_ns(s, n) {
+ printf(" `- %s lba size:%d lba max:%" PRIu64 "\n",
+ nvme_ns_get_name(n),
+ nvme_ns_get_lba_size(n),
+ nvme_ns_get_lba_count(n));
+ }
+ }
+ printf("\n");
+ }
+
+ printf("Test identification, logs, and features\n");
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ test_ctrl(c);
+ printf("\n");
+ nvme_ctrl_for_each_ns(c, n) {
+ test_namespace(n);
+ printf("\n");
+ }
+ }
+ nvme_subsystem_for_each_ns(s, n) {
+ test_namespace(n);
+ printf("\n");
+ }
+ }
+ }
+ nvme_free_tree(r);
+
+ return 0;
+}
diff --git a/test/tree.c b/test/tree.c
new file mode 100644
index 0000000..c9370f9
--- /dev/null
+++ b/test/tree.c
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2023 Daniel Wagner, SUSE LLC
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <ccan/array_size/array_size.h>
+
+#include <libnvme.h>
+#include <nvme/private.h>
+
+struct test_data {
+ /* input data */
+ const char *subsysname;
+ const char *subsysnqn;
+ const char *transport;
+ const char *traddr;
+ const char *host_traddr;
+ const char *host_iface;
+ const char *trsvcid;
+
+ /* track controller generated by input data */
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ int ctrl_id;
+};
+
+#define DEFAULT_SUBSYSNAME "subsysname"
+#define DEFAULT_SUBSYSNQN "subsysnqn"
+#define SRC_ADDR4 "192.168.56.100"
+#define SRC_ADDR6 "1234:5678:abcd:EF01:1234:5678:abcd:EF01"
+
+struct test_data test_data[] = {
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.1", "192.168.1.20", NULL, "4420" },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.1", "192.168.1.20", NULL, "4421" },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.2", "192.168.1.20", "eth1", "4420" },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "tcp", "192.168.1.2", "192.168.1.20", "eth1", "4421" },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "rdma", "192.168.1.3", "192.168.1.20", NULL, NULL },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "rdma", "192.168.1.4", "192.168.1.20", NULL, NULL },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "fc",
+ "nn-0x201700a09890f5bf:pn-0x201900a09890f5bf",
+ "nn-0x200000109b579ef3:pn-0x100000109b579ef3"
+ },
+ { DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN, "fc",
+ "nn-0x201700a09890f5bf:pn-0x201900a09890f5bf",
+ "nn-0x200000109b579ef6:pn-0x100000109b579ef6",
+ },
+};
+
+static struct test_data *find_test_data(nvme_ctrl_t c)
+{
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
+ struct test_data *d = &test_data[i];
+
+ if (d->c == c)
+ return d;
+ }
+
+ return NULL;
+}
+
+static void show_ctrl(nvme_ctrl_t c)
+{
+ struct test_data *d = find_test_data(c);
+
+ if (d)
+ printf("ctrl%d ", d->ctrl_id);
+ else
+ printf(" ");
+
+ printf("0x%p: %s %s %s %s %s ",
+ c,
+ nvme_ctrl_get_transport(c),
+ nvme_ctrl_get_traddr(c),
+ nvme_ctrl_get_host_traddr(c),
+ nvme_ctrl_get_host_iface(c),
+ nvme_ctrl_get_trsvcid(c));
+}
+
+static bool match_ctrl(struct test_data *d, nvme_ctrl_t c)
+{
+ bool pass = true;
+ const char *trsvid, *host_traddr, *host_iface;
+
+ if (d->c != c)
+ pass = false;
+
+ if (strcmp(d->transport, nvme_ctrl_get_transport(d->c)))
+ pass = false;
+
+ if (strcmp(d->traddr, nvme_ctrl_get_traddr(d->c)))
+ pass = false;
+
+
+ host_traddr = nvme_ctrl_get_host_traddr(c);
+ if (d->host_traddr &&
+ (!host_traddr || strcmp(d->host_traddr, host_traddr)))
+ pass = false;
+
+ host_iface = nvme_ctrl_get_host_iface(c);
+ if (d->host_iface &&
+ (!host_iface || strcmp(d->host_iface, host_iface)))
+ pass = false;
+
+ trsvid = nvme_ctrl_get_trsvcid(c);
+ if (d->trsvcid &&
+ (!trsvid || strcmp(d->trsvcid, trsvid)))
+ pass = false;
+
+ printf("[%s]", pass? "PASS" : "FAILED");
+
+ return pass;
+}
+
+static nvme_root_t create_tree()
+{
+ nvme_root_t r;
+ nvme_host_t h;
+
+ r = nvme_create_root(stdout, LOG_DEBUG);
+ assert(r);
+ h = nvme_default_host(r);
+ assert(h);
+
+ printf(" ctrls created:\n");
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
+ struct test_data *d = &test_data[i];
+
+ d->s = nvme_lookup_subsystem(h, d->subsysname, d->subsysnqn);
+ assert(d->s);
+ d->c = nvme_lookup_ctrl(d->s, d->transport, d->traddr,
+ d->host_traddr, d->host_iface,
+ d->trsvcid, NULL);
+ assert(d->c);
+ d->ctrl_id = i;
+
+ printf(" ");
+ show_ctrl(d->c);
+ match_ctrl(d, d->c);
+ printf("\n");
+ }
+ printf("\n");
+
+ return r;
+}
+
+static unsigned int count_entries(nvme_root_t r)
+{
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ unsigned int i = 0;
+
+ nvme_for_each_host(r, h)
+ nvme_for_each_subsystem(h, s)
+ nvme_subsystem_for_each_ctrl(s, c)
+ i++;
+
+ return i;
+}
+
+static bool tcp_ctrl_lookup(nvme_subsystem_t s, struct test_data *d)
+{
+ nvme_ctrl_t c;
+ bool pass = true;
+
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL,
+ NULL, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, "", "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+
+ if (d->host_traddr) {
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
+ NULL, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
+
+ if (d->host_iface) {
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, NULL,
+ d->host_iface, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, "", d->host_iface);
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
+
+ if (d->host_iface && d->traddr) {
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
+ d->host_iface, d->trsvcid, NULL);
+ printf("%10s %12s %10s -> ", d->trsvcid, d->host_traddr, d->host_iface);
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+ }
+
+ return pass;
+}
+
+static bool default_ctrl_lookup(nvme_subsystem_t s, struct test_data *d)
+{
+ nvme_ctrl_t c;
+ bool pass = true;
+
+ c = nvme_lookup_ctrl(s, d->transport, d->traddr, d->host_traddr,
+ NULL, NULL, NULL);
+ printf("%10s %12s %10s -> ", "", "", "");
+ show_ctrl(c);
+ pass &= match_ctrl(d, c);
+ printf("\n");
+
+ return pass;
+}
+
+static bool ctrl_lookups(nvme_root_t r)
+{
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ bool pass = true;
+
+ h = nvme_first_host(r);
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN);
+
+ printf(" lookup controller:\n");
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++) {
+ struct test_data *d = &test_data[i];
+
+ printf("%10s %12s %10s ", "", "", "");
+ show_ctrl(d->c);
+ printf("\n");
+
+ if (!strcmp("tcp", d->transport))
+ pass &= tcp_ctrl_lookup(s, d);
+ else
+ pass &= default_ctrl_lookup(s, d);
+
+ printf("\n");
+ }
+
+ return pass;
+}
+
+static bool test_lookup(void)
+{
+ nvme_root_t r;
+ bool pass;
+
+ printf("test_lookup:\n");
+
+ r = create_tree();
+ pass = count_entries(r) == ARRAY_SIZE(test_data);
+ pass &= ctrl_lookups(r);
+
+ nvme_free_tree(r);
+
+ return pass;
+}
+
+static bool test_src_addr()
+{
+ bool pass = true;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t c;
+ nvme_subsystem_t s;
+ char *src_addr, buffer[100]; /* big enough for IPv6 max length */
+
+ printf("\n"
+ "test_src_addr:\n");
+
+ r = nvme_create_root(stdout, LOG_DEBUG);
+ assert(r);
+
+ h = nvme_default_host(r);
+ assert(h);
+
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, DEFAULT_SUBSYSNQN);
+ assert(s);
+
+ c = nvme_lookup_ctrl(s, "tcp", "192.168.56.1", NULL, NULL, "8009", NULL);
+ assert(c);
+
+ c->address = NULL;
+ printf(" - Test c->address = NULL : src_addr = NULL ");
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=NULL should return src_addr=NULL\n");
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "";
+ printf(" - Test c->address = \"\" : src_addr = NULL ");
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address="" should return src_addr=NULL\n");
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = NULL ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (src_addr != NULL) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=NULL\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,trsvcid=8009,src_addr=" SRC_ADDR4;
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR4)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=192.168.56.1,src_addr=" SRC_ADDR4 ",trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR4 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR4)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR4 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6;
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 ",trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,trsvcid=8009,src_addr=" SRC_ADDR6 "%scope";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = "traddr=1234::abcd,src_addr=" SRC_ADDR6 "%scope,trsvcid=8009";
+ printf(" - Test c->address = \"%s\" : src_addr = \"" SRC_ADDR6 "\" ", c->address);
+ src_addr = nvme_ctrl_get_src_addr(c, buffer, sizeof(buffer));
+ if (!src_addr || strcmp(src_addr, SRC_ADDR6)) {
+ printf("[FAIL]\n");
+ fprintf(stderr,
+ "nvme_ctrl_get_src_addr() c->address=%s should return src_addr=" SRC_ADDR6 "\n",
+ c->address);
+ pass = false;
+ } else {
+ printf("[PASS]\n");
+ }
+
+ c->address = NULL; /* Needed to avoid freeing non-malloced memory (see above) */
+
+ nvme_free_tree(r);
+
+ return pass;
+}
+
+struct ctrl_args {
+ const char *transport;
+ const char *traddr;
+ const char *trsvcid;
+ const char *host_traddr;
+ const char *host_iface;
+ const char *address;
+ const char *subsysnqn;
+};
+
+static void set_ctrl_args(struct ctrl_args *args,
+ const char *transport,
+ const char *traddr,
+ const char *trsvcid,
+ const char *host_traddr,
+ const char *host_iface,
+ const char *address,
+ const char *subsysnqn)
+{
+ args->transport = transport;
+ args->traddr = traddr;
+ args->trsvcid = trsvcid;
+ args->host_traddr = host_traddr;
+ args->host_iface = host_iface;
+ args->address = address;
+ args->subsysnqn = subsysnqn;
+}
+
+static bool ctrl_match(const char *tag,
+ int reference_id,
+ int candidate_id,
+ struct ctrl_args *reference,
+ struct ctrl_args *candidate,
+ bool should_match)
+{
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */
+ nvme_ctrl_t candidate_ctrl;
+ nvme_ctrl_t found_ctrl;
+ nvme_subsystem_t s;
+
+ r = nvme_create_root(stdout, LOG_INFO);
+ assert(r);
+
+ h = nvme_default_host(r);
+ assert(h);
+
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN);
+ assert(s);
+
+ reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr,
+ reference->host_traddr, reference->host_iface,
+ reference->trsvcid, NULL);
+ assert(reference_ctrl);
+ reference_ctrl->name = "nvme1"; /* fake the device name */
+ if (reference->address) {
+ reference_ctrl->address = (char *)reference->address;
+ }
+
+ /* nvme_ctrl_find() MUST BE RUN BEFORE nvme_lookup_ctrl() */
+ found_ctrl = nvme_ctrl_find(s, candidate->transport, candidate->traddr,
+ candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr,
+ candidate->host_iface);
+
+ candidate_ctrl = nvme_lookup_ctrl(s, candidate->transport, candidate->traddr,
+ candidate->host_traddr, candidate->host_iface,
+ candidate->trsvcid, NULL);
+
+ if (should_match) {
+ if (candidate_ctrl != reference_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to match (%s, %s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface,
+ reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn,
+ reference->host_traddr, reference->host_iface, reference->address);
+ return false;
+ }
+
+ if (!found_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) failed to find controller\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ } else {
+ if (candidate_ctrl == reference_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not match (%s, %s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface,
+ reference->transport, reference->traddr, reference->trsvcid, reference->subsysnqn,
+ reference->host_traddr, reference->host_iface, reference->address);
+ return false;
+ }
+
+ if (found_ctrl) {
+ printf("%s-%d-%d: Candidate (%s, %s, %s, %s, %s, %s) should not have found controller. found_ctrl=%p reference=%p\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr, candidate->host_iface, found_ctrl, reference_ctrl);
+ return false;
+ }
+ }
+
+ /* Set the faked data back to NULL before freeing the tree */
+ reference_ctrl->name = NULL;
+ reference_ctrl->address = NULL;
+
+ nvme_free_tree(r);
+
+ return true;
+}
+
+/**
+ * test_ctrl_match_fc - Test that we can look up FC controllers
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_fc(void)
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("test_ctrl_match_fc:\n");
+
+ /*******************************************************************/
+ /* Reference ID 1 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:21", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 1, 5, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 1, 6, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 2 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 2, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 2, 5, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 3 */
+ set_ctrl_args(&reference, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", "21:00:00:e0:8b:05:05:20", NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "fc", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("FC", 3, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "fc", "21:00:00:e0:8b:05:05:01", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("FC", 3, 5, &reference, &candidate, false);
+
+ return pass;
+}
+
+/**
+ * test_ctrl_match_rdma - Test that we can look up RDMA controllers
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_rdma(void)
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("test_ctrl_match_rdma:\n");
+
+ /*******************************************************************/
+ /* Reference ID 1 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 5, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 1, 6, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 2 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 2, 5, &reference, &candidate, false);
+
+
+ /*******************************************************************/
+ /* Reference ID 3 */
+ set_ctrl_args(&reference, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 0, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 1, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 2, &reference, &candidate, true);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.2", "4420", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 3, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 4, &reference, &candidate, false);
+
+ set_ctrl_args(&candidate, "rdma", "192.168.2.1", "8009", NULL, "", NULL, NULL);
+ pass &= ctrl_match("RDMA", 3, 5, &reference, &candidate, false);
+
+ return pass;
+}
+
+/**
+ * test_ctrl_match_tcp - Test that we can look up TCP controllers
+ *
+ * @note: The mocked getifaddrs() returns 2 interface entries with the
+ * following addresses. Therefore the tests must use IP addresses
+ * that match these.
+ *
+ * eth0
+ * \_ 192.168.1.20
+ * \_ fe80::dead:beef
+ *
+ * lo
+ * \_ 127.0.0.1
+ * \_ ::1
+ *
+ * @return true when all tests have passed. false otherwise.
+ */
+static bool test_ctrl_match_tcp()
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("\n"
+ "test_ctrl_match_tcp:\n");
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 1 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 1, 8, &reference, &candidate, true);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 2 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 2, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 3 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 3, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 4 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 4, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 5 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 5, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 6 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=192.168.1.20", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 6, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv4: Reference ID 7 */
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, "traddr=123.123.123.123,trsvcid=8009,src_addr=127.0.0.1", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv4", 7, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 1 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 1, 8, &reference, &candidate, true);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 2 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 2, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 3 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 3, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 4 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 4, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 5 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 5, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 6 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=fe80::dead:beef", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 6, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 6, 8, &reference, &candidate, false);
+
+ /*******************************************************************/
+ /* IPv6: Reference ID 7 */
+ set_ctrl_args(&reference, "tcp", "aaaa::bbbb", "8009", NULL, NULL, "traddr=aaaa::bbbb,trsvcid=8009,src_addr=::1", NULL);
+
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 1, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 2, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 3, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", NULL, NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 4, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "eth0", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 5, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:beef", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 7, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "aaaa::bbbb", "8009", "fe80::dead:cafe", "lo", NULL, NULL);
+ pass &= ctrl_match("IPv6", 7, 8, &reference, &candidate, false);
+
+ return pass;
+}
+
+static bool ctrl_config_match(const char *tag,
+ int reference_id,
+ int candidate_id,
+ struct ctrl_args *reference,
+ struct ctrl_args *candidate,
+ bool should_match)
+{
+ bool match;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t reference_ctrl; /* Existing controller (from sysfs) */
+ nvme_subsystem_t s;
+
+ r = nvme_create_root(stdout, LOG_INFO);
+ assert(r);
+
+ h = nvme_default_host(r);
+ assert(h);
+
+ s = nvme_lookup_subsystem(h, DEFAULT_SUBSYSNAME, reference->subsysnqn ? reference->subsysnqn : DEFAULT_SUBSYSNQN);
+ assert(s);
+
+ reference_ctrl = nvme_lookup_ctrl(s, reference->transport, reference->traddr,
+ reference->host_traddr, reference->host_iface,
+ reference->trsvcid, NULL);
+ assert(reference_ctrl);
+ reference_ctrl->name = "nvme1"; /* fake the device name */
+ if (reference->address) {
+ reference_ctrl->address = (char *)reference->address;
+ }
+
+ match = nvme_ctrl_config_match(reference_ctrl, candidate->transport, candidate->traddr,
+ candidate->trsvcid, candidate->subsysnqn,
+ candidate->host_traddr, candidate->host_iface);
+
+ if (should_match) {
+ if (!match) {
+ printf("%s-%d-%d: Failed to match config for Candidate (%s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ } else {
+ if (match) {
+ printf("%s-%d-%d: Config should not have matched for Candidate (%s, %s, %s, %s, %s, %s)\n",
+ tag, reference_id, candidate_id,
+ candidate->transport, candidate->traddr, candidate->trsvcid,
+ candidate->subsysnqn, candidate->host_traddr, candidate->host_iface);
+ return false;
+ }
+ }
+
+ /* Set the faked data back to NULL before freeing the tree */
+ reference_ctrl->name = NULL;
+ reference_ctrl->address = NULL;
+
+ nvme_free_tree(r);
+
+ return true;
+}
+
+static bool test_ctrl_config_match()
+{
+ bool pass = true;
+ struct ctrl_args reference = {0};
+ struct ctrl_args candidate = {0};
+
+ printf("\n"
+ "test_ctrl_config_match:\n");
+
+ set_ctrl_args(&reference, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 0, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 1, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 2, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 3, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", NULL, NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 4, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "eth0", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 5, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", NULL, "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 6, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.20", "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 7, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, NULL);
+ pass &= ctrl_config_match("IPv4", 1, 8, &reference, &candidate, true);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, "hello");
+ pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, false);
+ set_ctrl_args(&candidate, "tcp", "123.123.123.123", "8009", "192.168.1.21", "lo", NULL, DEFAULT_SUBSYSNQN);
+ pass &= ctrl_config_match("IPv4", 1, 9, &reference, &candidate, true);
+
+ return pass;
+}
+
+
+/**
+ * This test module uses a mocked ifaddrs library (mock-ifaddrs.c)
+ * such that there are 2 fake interfaces (eth0 and lo) with the
+ * following IP addresses:
+ *
+ * - eth0
+ * \_ IPv4: 192.168.1.20
+ * \_ IPv6: fe80::dead:beef
+ *
+ * - lo
+ * \_ IPv4: 127.0.0.1
+ * \_ IPv6: ::1
+ */
+int main(int argc, char *argv[])
+{
+ bool pass = true;
+
+ pass &= test_lookup();
+ pass &= test_src_addr();
+ pass &= test_ctrl_match_fc();
+ pass &= test_ctrl_match_rdma();
+ pass &= test_ctrl_match_tcp();
+ pass &= test_ctrl_config_match();
+
+ fflush(stdout);
+
+ exit(pass ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/test/tree.py b/test/tree.py
new file mode 100644
index 0000000..626a0aa
--- /dev/null
+++ b/test/tree.py
@@ -0,0 +1,23 @@
+#!/usr/bin/python3
+'''
+SPDX-License-Identifier: LGPL-3.1-or-later
+
+This file is part of libnvme.
+Copyright (c) 2021 SUSE Software Solutions AG
+
+Authors: Hannes Reinecke <hare@suse.de>
+
+Scans the NVMe subsystem and prints out all found hosts,
+subsystems, and controllers
+'''
+
+import libnvme
+
+r = libnvme.nvme_root()
+for h in r.hosts():
+ print (h)
+ for s in h.subsystems():
+ print (s)
+ for c in s.controllers():
+ print (c)
+
diff --git a/test/utils.c b/test/utils.c
new file mode 100644
index 0000000..60665b8
--- /dev/null
+++ b/test/utils.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libnvme.
+ *
+ * Common test utilities.
+ *
+ * Copyright (c) 2022 Code Construct
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+FILE *test_setup_log(void)
+{
+ FILE *fd;
+
+ fd = tmpfile();
+ if (!fd)
+ err(EXIT_FAILURE, "can't create temporary file for log buf");
+
+ return fd;
+}
+
+void test_close_log(FILE *fd)
+{
+ fclose(fd);
+}
+
+void test_print_log_buf(FILE *logfd)
+{
+ char buf[4096];
+ int rc;
+
+ if (!ftell(logfd))
+ return;
+
+ rewind(logfd);
+
+ printf("--- begin test output\n");
+
+ while (!feof(logfd) && !ferror(logfd)) {
+ size_t rlen, wlen, wpos;
+
+ rlen = fread(buf, 1, sizeof(buf), logfd);
+ if (rlen <= 0)
+ break;
+
+ for (wpos = 0; wpos < rlen;) {
+ wlen = fwrite(buf + wpos, 1, rlen - wpos, stdout);
+ if (wlen == 0)
+ break;
+ wpos += wlen;
+ }
+
+ if (feof(logfd) || ferror((logfd)))
+ break;
+ }
+
+ printf("--- end test output\n");
+ rewind(logfd);
+ rc = ftruncate(fileno(logfd), 0);
+ if (rc)
+ printf("failed to truncate log buf; further output may be invalid\n");
+}
+
diff --git a/test/utils.h b/test/utils.h
new file mode 100644
index 0000000..e86f6e6
--- /dev/null
+++ b/test/utils.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libnvme.
+ * Copyright (c) 2022 Code Construct
+ *
+ * Common test utilities for libnvme tests. These have quite strict error
+ * handling, so the general pattern is to abort/exit on error.
+ */
+
+#ifndef _TEST_UTILS_H
+#define _TEST_UTILS_H
+
+#include <stdio.h>
+
+FILE *test_setup_log(void);
+void test_print_log_buf(FILE *logfd);
+void test_close_log(FILE *fd);
+
+#endif /* _TEST_UTILS_H */
+
diff --git a/test/uuid.c b/test/uuid.c
new file mode 100644
index 0000000..9146453
--- /dev/null
+++ b/test/uuid.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2022 Daniel Wagner, SUSE Software Solutions
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <ccan/array_size/array_size.h>
+
+#include <libnvme.h>
+
+static int test_rc;
+
+struct test_data {
+ unsigned char uuid[NVME_UUID_LEN];
+ const char *str;
+};
+
+static struct test_data test_data[] = {
+ { { 0 }, "00000000-0000-0000-0000-000000000000" },
+ { { [0 ... 15] = 0xff }, "ffffffff-ffff-ffff-ffff-ffffffffffff" },
+ { { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0f, 0x10 },
+ "00010203-0405-0607-0809-0a0b0c0d0f10" },
+};
+
+static void check_str(const char *exp, const char *res)
+{
+ if (!strcmp(res, exp))
+ return;
+
+ printf("ERROR: got '%s', expected '%s'\n", res, exp);
+
+ test_rc = 1;
+}
+
+static void print_uuid_hex(const unsigned char uuid[NVME_UUID_LEN])
+{
+ for (int i = 0; i < NVME_UUID_LEN; i++)
+ printf("%02x", uuid[i]);
+}
+
+static void check_uuid(unsigned char exp[NVME_UUID_LEN],
+ unsigned char res[NVME_UUID_LEN])
+{
+ if (!memcmp(exp, res, NVME_UUID_LEN))
+ return;
+
+ printf("ERROR: got '");
+ print_uuid_hex(exp);
+ printf("', expected '");
+ print_uuid_hex(res);
+ printf("'\n");
+}
+
+static void tostr_test(struct test_data *test)
+{
+ char str[NVME_UUID_LEN_STRING];
+
+ if (nvme_uuid_to_string(test->uuid, str)) {
+ test_rc = 1;
+ printf("ERROR: nvme_uuid_to_string() failed\n");
+ return;
+ }
+ check_str(test->str, str);
+}
+
+static void fromstr_test(struct test_data *test)
+{
+
+ unsigned char uuid[NVME_UUID_LEN];
+
+ if (nvme_uuid_from_string(test->str, uuid)) {
+ test_rc = 1;
+ printf("ERROR: nvme_uuid_from_string() failed\n");
+ return;
+ }
+ check_uuid(test->uuid, uuid);
+}
+
+static void random_uuid_test(void)
+{
+ unsigned char uuid1[NVME_UUID_LEN], uuid2[NVME_UUID_LEN];
+ char str1[NVME_UUID_LEN_STRING], str2[NVME_UUID_LEN_STRING];
+
+ if (nvme_uuid_random(uuid1) || nvme_uuid_random(uuid2)) {
+ test_rc = 1;
+ printf("ERROR: nvme_uuid_random() failed\n");
+ return;
+ }
+
+ if (!memcmp(uuid1, uuid2, NVME_UUID_LEN)) {
+ test_rc = 1;
+ printf("ERROR: generated random numbers are equal\n");
+ return;
+ }
+
+ if (nvme_uuid_to_string(uuid1, str1) ||
+ nvme_uuid_to_string(uuid2, str2)) {
+ test_rc = 1;
+ printf("ERROR: could not stringify randomly generated UUID\n");
+ return;
+ }
+ printf("PASS: generated UUIDs %s %s\n", str1, str2);
+}
+
+int main(void)
+{
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++)
+ tostr_test(&test_data[i]);
+
+ for (int i = 0; i < ARRAY_SIZE(test_data); i++)
+ fromstr_test(&test_data[i]);
+
+ random_uuid_test();
+
+ return test_rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/test/zns.c b/test/zns.c
new file mode 100644
index 0000000..6d06d72
--- /dev/null
+++ b/test/zns.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2020 Western Digital Corporation or its affiliates.
+ *
+ * Authors: Keith Busch <keith.busch@wdc.com>
+ */
+
+/**
+ * Search out for ZNS type namespaces, and if found, report their properties.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <libnvme.h>
+#include <inttypes.h>
+
+#include <ccan/endian/endian.h>
+
+static void show_zns_properties(nvme_ns_t n)
+{
+ struct nvme_zns_id_ns zns_ns;
+ struct nvme_zns_id_ctrl zns_ctrl;
+ struct nvme_zone_report *zr;
+ __u32 result;
+
+ zr = calloc(1, 0x1000);
+ if (!zr)
+ return;
+
+ if (nvme_zns_identify_ns(nvme_ns_get_fd(n), nvme_ns_get_nsid(n),
+ &zns_ns)) {
+ fprintf(stderr, "failed to identify zns ns\n");;
+ }
+
+ printf("zoc:%x ozcs:%x mar:%x mor:%x\n", le16_to_cpu(zns_ns.zoc),
+ le16_to_cpu(zns_ns.ozcs), le32_to_cpu(zns_ns.mar),
+ le32_to_cpu(zns_ns.mor));
+
+ if (nvme_zns_identify_ctrl(nvme_ns_get_fd(n), &zns_ctrl)) {
+ fprintf(stderr, "failed to identify zns ctrl\n");;
+ free(zr);
+ return;
+ }
+
+ printf("zasl:%u\n", zns_ctrl.zasl);
+
+ if (nvme_zns_report_zones(nvme_ns_get_fd(n), nvme_ns_get_nsid(n), 0,
+ NVME_ZNS_ZRAS_REPORT_ALL, false,
+ true, 0x1000, (void *)zr,
+ NVME_DEFAULT_IOCTL_TIMEOUT, &result)) {
+ fprintf(stderr, "failed to report zones, result %x\n",
+ le32_to_cpu(result));
+ free(zr);
+ return;
+ }
+
+ printf("nr_zones:%"PRIu64"\n", le64_to_cpu(zr->nr_zones));
+ free(zr);
+}
+
+int main()
+{
+ nvme_subsystem_t s;
+ nvme_root_t r;
+ nvme_host_t h;
+ nvme_ctrl_t c;
+ nvme_ns_t n;
+
+ r = nvme_scan(NULL);
+ if (!r)
+ return -1;
+
+ nvme_for_each_host(r, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ctrl(s, c) {
+ nvme_ctrl_for_each_ns(c, n) {
+ if (nvme_ns_get_csi(n) == NVME_CSI_ZNS)
+ show_zns_properties(n);
+ }
+ }
+ nvme_subsystem_for_each_ns(s, n) {
+ if (nvme_ns_get_csi(n) == NVME_CSI_ZNS)
+ show_zns_properties(n);
+ }
+ }
+ }
+ nvme_free_tree(r);
+}