diff options
Diffstat (limited to '')
-rw-r--r-- | test/meson.build | 39 | ||||
-rw-r--r-- | test/mi-mctp.c | 462 | ||||
-rw-r--r-- | test/mi.c | 809 | ||||
-rw-r--r-- | test/register.c | 2 | ||||
-rw-r--r-- | test/test.c | 2 | ||||
-rw-r--r-- | test/utils.c | 68 | ||||
-rw-r--r-- | test/utils.h | 20 | ||||
-rw-r--r-- | test/zns.c | 2 |
8 files changed, 1393 insertions, 11 deletions
diff --git a/test/meson.build b/test/meson.build index 193d558..00c9ceb 100644 --- a/test/meson.build +++ b/test/meson.build @@ -12,28 +12,47 @@ main = executable( 'main-test', ['test.c'], - dependencies: libuuid_dep, - link_with: libnvme, + dependencies: [libnvme_dep, libuuid_dep], include_directories: [incdir, internal_incdir] ) -cpp = executable( - 'test-cpp', - ['cpp.cc'], - link_with: libnvme, - 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'], - link_with: libnvme, + dependencies: libnvme_dep, include_directories: [incdir, internal_incdir] ) zns = executable( 'test-zns', ['zns.c'], - link_with: libnvme, + 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) diff --git a/test/mi-mctp.c b/test/mi-mctp.c new file mode 100644 index 0000000..a831cf7 --- /dev/null +++ b/test/mi-mctp.c @@ -0,0 +1,462 @@ +// 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); + +/* 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; + + /* 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; + + assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ); + + crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len); + *(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc); + peer->tx_buf_len += sizeof(crc); +} + +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); +} + +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, + 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 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 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(rc == 0x2); +} + +/* 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(rc == 2); + } + + nvme_mi_close_ctrl(ctrl); +} + +/* test: unaligned response sizes - should always report a transport error */ +static void test_admin_resp_sizes_unaligned(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++) { + peer->tx_buf_len = i; + if (!(i & 0x3)) + continue; + rc = nvme_mi_admin_identify_ctrl(ctrl, &id); + assert(rc < 0); + } + + nvme_mi_close_ctrl(ctrl); +} + +/* test: send a More Processing Required response, then the actual response */ +struct mpr_tx_info { + int msg_no; + 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; + 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); + + 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); + + 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); +} + + +#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(mi_resp_err), + DEFINE_TEST(admin_resp_err), + DEFINE_TEST(admin_resp_sizes), + DEFINE_TEST(admin_resp_sizes_unaligned), + DEFINE_TEST(mpr_mi), + DEFINE_TEST(mpr_admin), +}; + +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..d269060 --- /dev/null +++ b/test/mi.c @@ -0,0 +1,809 @@ +// 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> + +/* 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); + 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 */ +static int test_admin_err_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_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_resp_cb, NULL); + + ctrl = nvme_mi_init_ctrl(ep, 1); + assert(ctrl); + + rc = nvme_mi_admin_identify_ctrl(ctrl, &id); + assert(rc != 0); +} + +/* 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 nvme_mi_admin_resp_hdr resp = { 0 }; + struct nvme_mi_admin_req_hdr req = { 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, 1, &resp, 0, &len); + assert(rc != 0); + + /* unaligned resp size */ + len = 1; + rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 0, &len); + assert(rc != 0); + + /* unaligned resp offset */ + len = 4; + rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 1, &len); + assert(rc != 0); + + /* resp too large */ + len = 4096 + 4; + rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 0, &len); + assert(rc != 0); + + /* resp offset too large */ + len = 4; + rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, (off_t)1 << 32, &len); + assert(rc != 0); + + /* resp offset with no len */ + len = 0; + rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 4, &len); + assert(rc != 0); + + /* req and resp payloads */ + len = 4; + rc = nvme_mi_admin_xfer(ctrl, &req, 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); +} + +#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_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), +}; + +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/register.c b/test/register.c index 8791083..8a41628 100644 --- a/test/register.c +++ b/test/register.c @@ -11,6 +11,8 @@ * 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> diff --git a/test/test.c b/test/test.c index bf13412..bc5393b 100644 --- a/test/test.c +++ b/test/test.c @@ -19,7 +19,7 @@ #include <string.h> #include <stdbool.h> #include <inttypes.h> -#include <uuid/uuid.h> +#include <uuid.h> #include <libnvme.h> #include <ccan/endian/endian.h> 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 */ + @@ -40,6 +40,7 @@ static void show_zns_properties(nvme_ns_t n) if (nvme_zns_identify_ctrl(nvme_ns_get_fd(n), &zns_ctrl)) { fprintf(stderr, "failed to identify zns ctrl\n");; + free(zr); return; } @@ -51,6 +52,7 @@ static void show_zns_properties(nvme_ns_t n) NVME_DEFAULT_IOCTL_TIMEOUT, &result)) { fprintf(stderr, "failed to report zones, result %x\n", le32_to_cpu(result)); + free(zr); return; } |