1
0
Fork 0
libnvme/test/ioctl/discovery.c
Daniel Baumann 6add9877e4
Merging upstream version 1.6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-16 10:12:19 +01:00

460 lines
14 KiB
C

// 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
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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header), /* LPOL */
.out_data = log_entries,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header),
.cdw10 = (sizeof(header) / 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 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 fetch is retried
*/
struct mock_cmd mock_admin_cmds[] = {
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header1),
.cdw10 = (sizeof(header1) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* NUMDL */
.cdw12 = sizeof(header1), /* LPOL */
.out_data = entries1,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_DISCOVER, /* LID */
.out_data = &header2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header2), /* LPOL */
.out_data = log_entries2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 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 = sizeof(header1),
.cdw10 = (sizeof(header1) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header1), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| NVME_LOG_LID_DISCOVER, /* LID */
.out_data = &header2,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header2),
.cdw10 = (sizeof(header2) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header2), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header3),
.cdw10 = (sizeof(header3) / 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)
{
size_t header_size = sizeof(struct nvmf_discovery_log);
/* 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_size,
.cdw10 = (header_size / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| 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 = sizeof(header),
.cdw10 = (sizeof(header) / 4 - 1) << 16 /* NUMDL */
| 1 << 15 /* RAE */
| 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 */
| 1 << 15 /* RAE */
| NVME_LOG_LID_DISCOVER, /* LID */
.cdw12 = sizeof(header), /* LPOL */
.out_data = &entry,
},
{
.opcode = nvme_admin_get_log_page,
.data_len = sizeof(header),
.cdw10 = (sizeof(header) / 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);
}