diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/cpp.cc | 66 | ||||
-rw-r--r-- | test/meson.build | 67 | ||||
-rw-r--r-- | test/mi-mctp.c | 718 | ||||
-rw-r--r-- | test/mi.c | 1938 | ||||
-rw-r--r-- | test/register.c | 233 | ||||
-rw-r--r-- | test/test.c | 431 | ||||
-rw-r--r-- | test/tree.py | 23 | ||||
-rw-r--r-- | test/utils.c | 68 | ||||
-rw-r--r-- | test/utils.h | 20 | ||||
-rw-r--r-- | test/uuid.c | 120 | ||||
-rw-r--r-- | test/zns.c | 90 |
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); +} |