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