summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/cpp.cc66
-rw-r--r--test/meson.build67
-rw-r--r--test/mi-mctp.c718
-rw-r--r--test/mi.c1938
-rw-r--r--test/register.c233
-rw-r--r--test/test.c431
-rw-r--r--test/tree.py23
-rw-r--r--test/utils.c68
-rw-r--r--test/utils.h20
-rw-r--r--test/uuid.c120
-rw-r--r--test/zns.c90
11 files changed, 3774 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/meson.build b/test/meson.build
new file mode 100644
index 0000000..d90f835
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1,67 @@
+# 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)
diff --git a/test/mi-mctp.c b/test/mi-mctp.c
new file mode 100644
index 0000000..6d83d42
--- /dev/null
+++ b/test/mi-mctp.c
@@ -0,0 +1,718 @@
+// 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;
+
+ 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);
+}
+
+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 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: 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: 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(admin_resp_err),
+ DEFINE_TEST(admin_resp_sizes),
+ DEFINE_TEST(admin_resp_sizes_unaligned),
+ 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..5bbb2f0
--- /dev/null
+++ b/test/mi.c
@@ -0,0 +1,1938 @@
+// 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);
+ 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;
+ __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_SECONDARY_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_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, 0x01020304,
+ 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_id_ns *id;
+ __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_id_ns));
+ id = req->data;
+
+ /* No NSID on created namespaces */
+ assert(nsid == 0);
+ assert(csi == 0);
+
+ /* allow operations on nsze == 42, reject others */
+ if (le64_to_cpu(id->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_id_ns nsid = { 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, &nsid, 0, &ns);
+ assert(!rc);
+ assert(ns == 0x01020304);
+
+ nsid.nsze = cpu_to_le64(42);
+ rc = nvme_mi_admin_ns_mgmt_create(ctrl, &nsid, 0, &ns);
+ 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 = 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 = 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/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/test.c b/test/test.c
new file mode 100644
index 0000000..2f24e1e
--- /dev/null
+++ b/test/test.c
@@ -0,0 +1,431 @@
+// 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 <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, 1, 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_recovery(fd, sel, &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_mask(fd, sel, &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_persist(fd, sel, &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 = { 0 };
+ __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);
+ ret = nvme_identify_ns_descs(fd, nsid, &descs);
+ if (!ret)
+ printf(" Identify NS Descriptors\n");
+ else
+ printf(" ERROR: Identify NS Descriptors:%x\n", ret);
+ 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.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);
+}