summaryrefslogtreecommitdiffstats
path: root/test/ioctl
diff options
context:
space:
mode:
Diffstat (limited to 'test/ioctl')
-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
8 files changed, 3008 insertions, 0 deletions
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 */