summaryrefslogtreecommitdiffstats
path: root/test/ioctl/discovery.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/ioctl/discovery.c428
1 files changed, 428 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);
+}