Merging upstream version 1.1~rc0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
537ee18b08
commit
73281abe5f
764 changed files with 32602 additions and 5874 deletions
|
@ -12,28 +12,47 @@
|
|||
main = executable(
|
||||
'main-test',
|
||||
['test.c'],
|
||||
dependencies: libuuid_dep,
|
||||
link_with: libnvme,
|
||||
dependencies: [libnvme_dep, libuuid_dep],
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
|
||||
cpp = executable(
|
||||
'test-cpp',
|
||||
['cpp.cc'],
|
||||
link_with: libnvme,
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
if cxx_available
|
||||
cpp = executable(
|
||||
'test-cpp',
|
||||
['cpp.cc'],
|
||||
dependencies: libnvme_dep,
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
endif
|
||||
|
||||
register = executable(
|
||||
'test-register',
|
||||
['register.c'],
|
||||
link_with: libnvme,
|
||||
dependencies: libnvme_dep,
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
|
||||
zns = executable(
|
||||
'test-zns',
|
||||
['zns.c'],
|
||||
link_with: libnvme,
|
||||
dependencies: libnvme_dep,
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
|
||||
mi = executable(
|
||||
'test-mi',
|
||||
['mi.c', 'utils.c'],
|
||||
dependencies: libnvme_mi_test_dep,
|
||||
include_directories: [incdir, internal_incdir]
|
||||
)
|
||||
|
||||
test('mi', mi)
|
||||
|
||||
mi_mctp = executable(
|
||||
'test-mi-mctp',
|
||||
['mi-mctp.c', 'utils.c'],
|
||||
dependencies: libnvme_mi_test_dep,
|
||||
include_directories: [incdir, internal_incdir],
|
||||
)
|
||||
|
||||
test('mi-mctp', mi_mctp)
|
||||
|
|
462
test/mi-mctp.c
Normal file
462
test/mi-mctp.c
Normal file
|
@ -0,0 +1,462 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/**
|
||||
* This file is part of libnvme.
|
||||
* Copyright (c) 2022 Code Construct
|
||||
*/
|
||||
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/endian/endian.h>
|
||||
|
||||
#include "libnvme-mi.h"
|
||||
#include "nvme/private.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* 4096 byte max MCTP message, plus space for header data */
|
||||
#define MAX_BUFSIZ 8192
|
||||
|
||||
struct test_peer;
|
||||
|
||||
typedef int (*rx_test_fn)(struct test_peer *peer, void *buf, size_t len);
|
||||
|
||||
/* Our fake MCTP "peer".
|
||||
*
|
||||
* The terms TX (transmit) and RX (receive) are from the perspective of
|
||||
* the NVMe device. TX is device-to-libnvme, RX is libnvme-to-device.
|
||||
*
|
||||
* The RX and TX buffers are linear versions of the data sent and received by
|
||||
* libnvme-mi, and *include* the MCTP message type byte (even though it's
|
||||
* omitted in the sendmsg/recvmsg interface), so that the buffer inspection
|
||||
* in the tests can exactly match the NVMe-MI spec packet diagrams.
|
||||
*/
|
||||
static struct test_peer {
|
||||
/* rx (sendmsg) data sent from libnvme, and return value */
|
||||
unsigned char rx_buf[MAX_BUFSIZ];
|
||||
size_t rx_buf_len;
|
||||
ssize_t rx_rc; /* if zero, return the sendmsg len */
|
||||
int rx_errno;
|
||||
|
||||
/* tx (recvmsg) data to be received by libnvme and return value */
|
||||
unsigned char tx_buf[MAX_BUFSIZ];
|
||||
size_t tx_buf_len;
|
||||
ssize_t tx_rc; /* if zero, return the recvmsg len */
|
||||
int tx_errno;
|
||||
|
||||
/* Optional, called before TX, may set tx_buf according to request.
|
||||
* Return value stored in tx_res, may be used by test */
|
||||
rx_test_fn tx_fn;
|
||||
void *tx_data;
|
||||
int tx_fn_res;
|
||||
|
||||
/* store sd from socket() setup */
|
||||
int sd;
|
||||
} test_peer;
|
||||
|
||||
/* ensure tests start from a standard state */
|
||||
void reset_test_peer(void)
|
||||
{
|
||||
int tmp = test_peer.sd;
|
||||
memset(&test_peer, 0, sizeof(test_peer));
|
||||
test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
|
||||
test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
|
||||
test_peer.sd = tmp;
|
||||
}
|
||||
|
||||
/* calculate MIC of peer-to-libnvme data, expand buf by 4 bytes and insert
|
||||
* the new MIC */
|
||||
static void test_set_tx_mic(struct test_peer *peer)
|
||||
{
|
||||
extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
|
||||
__u32 crc = 0xffffffff;
|
||||
|
||||
assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ);
|
||||
|
||||
crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len);
|
||||
*(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc);
|
||||
peer->tx_buf_len += sizeof(crc);
|
||||
}
|
||||
|
||||
int __wrap_socket(int family, int type, int protocol)
|
||||
{
|
||||
/* we do an open here to give the mi-mctp code something to close() */
|
||||
test_peer.sd = open("/dev/null", 0);
|
||||
return test_peer.sd;
|
||||
}
|
||||
|
||||
ssize_t __wrap_sendmsg(int sd, const struct msghdr *hdr, int flags)
|
||||
{
|
||||
size_t i, pos;
|
||||
|
||||
assert(sd == test_peer.sd);
|
||||
|
||||
test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
|
||||
|
||||
/* gather iovec into buf */
|
||||
for (i = 0, pos = 1; i < hdr->msg_iovlen; i++) {
|
||||
struct iovec *iov = &hdr->msg_iov[i];
|
||||
|
||||
assert(pos + iov->iov_len < MAX_BUFSIZ - 1);
|
||||
memcpy(test_peer.rx_buf + pos, iov->iov_base, iov->iov_len);
|
||||
pos += iov->iov_len;
|
||||
}
|
||||
|
||||
test_peer.rx_buf_len = pos;
|
||||
|
||||
errno = test_peer.rx_errno;
|
||||
|
||||
return test_peer.rx_rc ?: (pos - 1);
|
||||
}
|
||||
|
||||
ssize_t __wrap_recvmsg(int sd, struct msghdr *hdr, int flags)
|
||||
{
|
||||
size_t i, pos, len;
|
||||
|
||||
assert(sd == test_peer.sd);
|
||||
|
||||
if (test_peer.tx_fn) {
|
||||
test_peer.tx_fn_res = test_peer.tx_fn(&test_peer,
|
||||
test_peer.rx_buf,
|
||||
test_peer.rx_buf_len);
|
||||
} else {
|
||||
/* set up a few default response fields; caller may have
|
||||
* initialised the rest of the response */
|
||||
test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
|
||||
test_peer.tx_buf[1] = test_peer.rx_buf[1] | (NVME_MI_ROR_RSP << 7);
|
||||
test_set_tx_mic(&test_peer);
|
||||
}
|
||||
|
||||
/* scatter buf into iovec */
|
||||
for (i = 0, pos = 1; i < hdr->msg_iovlen && pos < test_peer.tx_buf_len;
|
||||
i++) {
|
||||
struct iovec *iov = &hdr->msg_iov[i];
|
||||
|
||||
len = iov->iov_len;
|
||||
if (len > test_peer.tx_buf_len - pos)
|
||||
len = test_peer.tx_buf_len - pos;
|
||||
|
||||
memcpy(iov->iov_base, test_peer.tx_buf + pos, len);
|
||||
pos += len;
|
||||
}
|
||||
|
||||
errno = test_peer.tx_errno;
|
||||
|
||||
return test_peer.tx_rc ?: (pos - 1);
|
||||
}
|
||||
|
||||
struct mctp_ioc_tag_ctl;
|
||||
|
||||
#ifdef SIOCMCTPALLOCTAG
|
||||
int test_ioctl_tag(int sd, unsigned long req, struct mctp_ioc_tag_ctl *ctl)
|
||||
{
|
||||
assert(sd == test_peer.sd);
|
||||
|
||||
switch (req) {
|
||||
case SIOCMCTPALLOCTAG:
|
||||
ctl->tag = 1 | MCTP_TAG_PREALLOC | MCTP_TAG_OWNER;
|
||||
break;
|
||||
case SIOCMCTPDROPTAG:
|
||||
assert(tag == 1 | MCTP_TAG_PREALLOC | MCTP_TAG_OWNER);
|
||||
break;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int test_ioctl_tag(int sd, unsigned long req, struct mctp_ioc_tag_ctl *ctl)
|
||||
{
|
||||
assert(sd == test_peer.sd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct __mi_mctp_socket_ops ops = {
|
||||
__wrap_socket,
|
||||
__wrap_sendmsg,
|
||||
__wrap_recvmsg,
|
||||
test_ioctl_tag,
|
||||
};
|
||||
|
||||
/* tests */
|
||||
static void test_rx_err(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
peer->rx_rc = -1;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
static int tx_none(struct test_peer *peer, void *buf, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_tx_none(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
peer->tx_buf_len = 0;
|
||||
peer->tx_fn = tx_none;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
static void test_tx_err(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
peer->tx_rc = -1;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
static void test_tx_short(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
peer->tx_buf_len = 11;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
static void test_read_mi_data(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
/* empty response data */
|
||||
peer->tx_buf_len = 8 + 32;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static void test_mi_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
/* simple error response */
|
||||
peer->tx_buf[4] = 0x02; /* internal error */
|
||||
peer->tx_buf_len = 8;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc == 0x2);
|
||||
}
|
||||
|
||||
static void test_admin_resp_err(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
int rc;
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
assert(ctrl);
|
||||
|
||||
/* Simple error response, will be shorter than the expected Admin
|
||||
* command response header. */
|
||||
peer->tx_buf[4] = 0x02; /* internal error */
|
||||
peer->tx_buf_len = 8;
|
||||
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc == 0x2);
|
||||
}
|
||||
|
||||
/* test: all 4-byte aligned response sizes - should be decoded into the
|
||||
* response status value. We use an admin command here as the header size will
|
||||
* be larger than the minimum header size (it contains the completion
|
||||
* doublewords), and we need to ensure that an error response is correctly
|
||||
* interpreted, including having the MIC extracted from the message.
|
||||
*/
|
||||
static void test_admin_resp_sizes(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
assert(ctrl);
|
||||
|
||||
peer->tx_buf[4] = 0x02; /* internal error */
|
||||
|
||||
for (i = 8; i <= 4096 + 8; i+=4) {
|
||||
peer->tx_buf_len = i;
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc == 2);
|
||||
}
|
||||
|
||||
nvme_mi_close_ctrl(ctrl);
|
||||
}
|
||||
|
||||
/* test: unaligned response sizes - should always report a transport error */
|
||||
static void test_admin_resp_sizes_unaligned(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
unsigned int i;
|
||||
int rc;
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
assert(ctrl);
|
||||
|
||||
peer->tx_buf[4] = 0x02; /* internal error */
|
||||
|
||||
for (i = 8; i <= 4096 + 8; i++) {
|
||||
peer->tx_buf_len = i;
|
||||
if (!(i & 0x3))
|
||||
continue;
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc < 0);
|
||||
}
|
||||
|
||||
nvme_mi_close_ctrl(ctrl);
|
||||
}
|
||||
|
||||
/* test: send a More Processing Required response, then the actual response */
|
||||
struct mpr_tx_info {
|
||||
int msg_no;
|
||||
size_t final_len;
|
||||
};
|
||||
|
||||
static int tx_mpr(struct test_peer *peer, void *buf, size_t len)
|
||||
{
|
||||
struct mpr_tx_info *tx_info = peer->tx_data;
|
||||
|
||||
memset(peer->tx_buf, 0, sizeof(peer->tx_buf));
|
||||
peer->tx_buf[0] = NVME_MI_MSGTYPE_NVME;
|
||||
peer->tx_buf[1] = test_peer.rx_buf[1] | (NVME_MI_ROR_RSP << 7);
|
||||
|
||||
switch (tx_info->msg_no) {
|
||||
case 1:
|
||||
peer->tx_buf[4] = NVME_MI_RESP_MPR;
|
||||
peer->tx_buf_len = 8;
|
||||
break;
|
||||
case 2:
|
||||
peer->tx_buf[4] = NVME_MI_RESP_SUCCESS;
|
||||
peer->tx_buf_len = tx_info->final_len;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
test_set_tx_mic(peer);
|
||||
|
||||
tx_info->msg_no++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_mpr_mi(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
struct mpr_tx_info tx_info;
|
||||
int rc;
|
||||
|
||||
tx_info.msg_no = 1;
|
||||
tx_info.final_len = sizeof(struct nvme_mi_mi_resp_hdr) + sizeof(ss_info);
|
||||
|
||||
peer->tx_fn = tx_mpr;
|
||||
peer->tx_data = &tx_info;
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static void test_mpr_admin(nvme_mi_ep_t ep, struct test_peer *peer)
|
||||
{
|
||||
struct mpr_tx_info tx_info;
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
int rc;
|
||||
|
||||
tx_info.msg_no = 1;
|
||||
tx_info.final_len = sizeof(struct nvme_mi_admin_resp_hdr) + sizeof(id);
|
||||
|
||||
peer->tx_fn = tx_mpr;
|
||||
peer->tx_data = &tx_info;
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc == 0);
|
||||
|
||||
nvme_mi_close_ctrl(ctrl);
|
||||
}
|
||||
|
||||
|
||||
#define DEFINE_TEST(name) { #name, test_ ## name }
|
||||
struct test {
|
||||
const char *name;
|
||||
void (*fn)(nvme_mi_ep_t, struct test_peer *);
|
||||
} tests[] = {
|
||||
DEFINE_TEST(rx_err),
|
||||
DEFINE_TEST(tx_none),
|
||||
DEFINE_TEST(tx_err),
|
||||
DEFINE_TEST(tx_short),
|
||||
DEFINE_TEST(read_mi_data),
|
||||
DEFINE_TEST(mi_resp_err),
|
||||
DEFINE_TEST(admin_resp_err),
|
||||
DEFINE_TEST(admin_resp_sizes),
|
||||
DEFINE_TEST(admin_resp_sizes_unaligned),
|
||||
DEFINE_TEST(mpr_mi),
|
||||
DEFINE_TEST(mpr_admin),
|
||||
};
|
||||
|
||||
static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep,
|
||||
struct test_peer *peer)
|
||||
{
|
||||
printf("Running test %s...", test->name);
|
||||
fflush(stdout);
|
||||
test->fn(ep, peer);
|
||||
printf(" OK\n");
|
||||
test_print_log_buf(logfd);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
nvme_root_t root;
|
||||
nvme_mi_ep_t ep;
|
||||
unsigned int i;
|
||||
FILE *fd;
|
||||
|
||||
fd = test_setup_log();
|
||||
|
||||
__nvme_mi_mctp_set_ops(&ops);
|
||||
|
||||
root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
|
||||
assert(root);
|
||||
|
||||
ep = nvme_mi_open_mctp(root, 0, 0);
|
||||
assert(ep);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
reset_test_peer();
|
||||
run_test(&tests[i], fd, ep, &test_peer);
|
||||
}
|
||||
|
||||
nvme_mi_close(ep);
|
||||
nvme_mi_free_root(root);
|
||||
|
||||
test_close_log(fd);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
809
test/mi.c
Normal file
809
test/mi.c
Normal file
|
@ -0,0 +1,809 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/**
|
||||
* This file is part of libnvme.
|
||||
* Copyright (c) 2022 Code Construct
|
||||
*/
|
||||
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ccan/array_size/array_size.h>
|
||||
|
||||
/* we define a custom transport, so need the internal headers */
|
||||
#include "nvme/private.h"
|
||||
|
||||
#include "libnvme-mi.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
typedef int (*test_submit_cb)(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data);
|
||||
|
||||
struct test_transport_data {
|
||||
unsigned int magic;
|
||||
bool named;
|
||||
test_submit_cb submit_cb;
|
||||
void *submit_cb_data;
|
||||
};
|
||||
|
||||
static const int test_transport_magic = 0x74657374;
|
||||
|
||||
static int test_transport_submit(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp)
|
||||
{
|
||||
struct test_transport_data *tpd = ep->transport_data;
|
||||
|
||||
assert(tpd->magic == test_transport_magic);
|
||||
|
||||
/* start from a minimal response: zeroed data, nmp to match request */
|
||||
memset(resp->hdr, 0, resp->hdr_len);
|
||||
memset(resp->data, 0, resp->data_len);
|
||||
resp->hdr->type = NVME_MI_MSGTYPE_NVME;
|
||||
resp->hdr->nmp = req->hdr->nmp | (NVME_MI_ROR_RSP << 7);
|
||||
|
||||
if (tpd->submit_cb)
|
||||
return tpd->submit_cb(ep, req, resp, tpd->submit_cb_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_transport_close(struct nvme_mi_ep *ep)
|
||||
{
|
||||
struct test_transport_data *tpd = ep->transport_data;
|
||||
assert(tpd->magic == test_transport_magic);
|
||||
free(tpd);
|
||||
}
|
||||
|
||||
static int test_transport_desc_ep(struct nvme_mi_ep *ep,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
struct test_transport_data *tpd = ep->transport_data;
|
||||
|
||||
assert(tpd->magic == test_transport_magic);
|
||||
|
||||
if (!tpd->named)
|
||||
return -1;
|
||||
|
||||
snprintf(buf, len, "test endpoint 0x%x", tpd->magic);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* internal test helper to generate correct response crc */
|
||||
static void test_transport_resp_calc_mic(struct nvme_mi_resp *resp)
|
||||
{
|
||||
extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
|
||||
__u32 crc = 0xffffffff;
|
||||
|
||||
crc = nvme_mi_crc32_update(crc, resp->hdr, resp->hdr_len);
|
||||
crc = nvme_mi_crc32_update(crc, resp->data, resp->data_len);
|
||||
|
||||
resp->mic = ~crc;
|
||||
}
|
||||
|
||||
static const struct nvme_mi_transport test_transport = {
|
||||
.name = "test-mi",
|
||||
.mic_enabled = true,
|
||||
.submit = test_transport_submit,
|
||||
.close = test_transport_close,
|
||||
.desc_ep = test_transport_desc_ep,
|
||||
};
|
||||
|
||||
static void test_set_transport_callback(nvme_mi_ep_t ep, test_submit_cb cb,
|
||||
void *data)
|
||||
{
|
||||
struct test_transport_data *tpd = ep->transport_data;
|
||||
assert(tpd->magic == test_transport_magic);
|
||||
|
||||
tpd->submit_cb = cb;
|
||||
tpd->submit_cb_data = data;
|
||||
}
|
||||
|
||||
nvme_mi_ep_t nvme_mi_open_test(nvme_root_t root)
|
||||
{
|
||||
struct test_transport_data *tpd;
|
||||
struct nvme_mi_ep *ep;
|
||||
|
||||
ep = nvme_mi_init_ep(root);
|
||||
assert(ep);
|
||||
|
||||
tpd = malloc(sizeof(*tpd));
|
||||
assert(tpd);
|
||||
|
||||
tpd->magic = test_transport_magic;
|
||||
tpd->named = true;
|
||||
|
||||
ep->transport = &test_transport;
|
||||
ep->transport_data = tpd;
|
||||
|
||||
return ep;
|
||||
}
|
||||
|
||||
unsigned int count_root_eps(nvme_root_t root)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
nvme_mi_ep_t ep;
|
||||
|
||||
nvme_mi_for_each_endpoint(root, ep)
|
||||
i++;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* test that the root->endpoints list is updated on endpoint
|
||||
* creation/destruction */
|
||||
static void test_endpoint_lifetime(nvme_mi_ep_t ep)
|
||||
{
|
||||
nvme_root_t root = ep->root;
|
||||
unsigned int count;
|
||||
nvme_mi_ep_t ep2;
|
||||
|
||||
count = count_root_eps(root);
|
||||
assert(count == 1);
|
||||
|
||||
ep2 = nvme_mi_open_test(root);
|
||||
count = count_root_eps(root);
|
||||
assert(count == 2);
|
||||
|
||||
nvme_mi_close(ep2);
|
||||
count = count_root_eps(root);
|
||||
assert(count == 1);
|
||||
}
|
||||
|
||||
unsigned int count_ep_controllers(nvme_mi_ep_t ep)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
|
||||
nvme_mi_for_each_ctrl(ep, ctrl)
|
||||
i++;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* test that the ep->controllers list is updated on controller
|
||||
* creation/destruction */
|
||||
static void test_ctrl_lifetime(nvme_mi_ep_t ep)
|
||||
{
|
||||
nvme_mi_ctrl_t c1, c2;
|
||||
int count;
|
||||
|
||||
ep->controllers_scanned = true;
|
||||
|
||||
count = count_ep_controllers(ep);
|
||||
assert(count == 0);
|
||||
|
||||
c1 = nvme_mi_init_ctrl(ep, 1);
|
||||
count = count_ep_controllers(ep);
|
||||
assert(count == 1);
|
||||
|
||||
c2 = nvme_mi_init_ctrl(ep, 2);
|
||||
count = count_ep_controllers(ep);
|
||||
assert(count == 2);
|
||||
|
||||
nvme_mi_close_ctrl(c1);
|
||||
count = count_ep_controllers(ep);
|
||||
assert(count == 1);
|
||||
|
||||
nvme_mi_close_ctrl(c2);
|
||||
count = count_ep_controllers(ep);
|
||||
assert(count == 0);
|
||||
}
|
||||
|
||||
|
||||
/* test: basic read MI datastructure command */
|
||||
static int test_read_mi_data_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
__u8 ror, mt, *hdr, *buf;
|
||||
|
||||
assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
|
||||
|
||||
ror = req->hdr->nmp >> 7;
|
||||
mt = req->hdr->nmp >> 3 & 0x7;
|
||||
assert(ror == NVME_MI_ROR_REQ);
|
||||
assert(mt == NVME_MI_MT_MI);
|
||||
|
||||
/* do we have enough for a mi header? */
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
|
||||
|
||||
/* inspect response as raw bytes */
|
||||
hdr = (__u8 *)req->hdr;
|
||||
assert(hdr[4] == nvme_mi_mi_opcode_mi_data_read);
|
||||
|
||||
/* create basic response */
|
||||
assert(resp->hdr_len >= sizeof(struct nvme_mi_mi_resp_hdr));
|
||||
assert(resp->data_len >= 4);
|
||||
|
||||
hdr = (__u8 *)resp->hdr;
|
||||
hdr[4] = 0; /* status */
|
||||
|
||||
buf = (__u8 *)resp->data;
|
||||
memset(buf, 0, resp->data_len);
|
||||
buf[0] = 1; /* NUMP */
|
||||
buf[1] = 1; /* MJR */
|
||||
buf[2] = 2; /* MNR */
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_read_mi_data(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_read_mi_data_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
/* test: failed transport */
|
||||
static int test_transport_fail_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_transport_fail(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_transport_fail_cb, NULL);
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
static void test_transport_describe(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct test_transport_data *tpd;
|
||||
char *str;
|
||||
|
||||
tpd = (struct test_transport_data *)ep->transport_data;
|
||||
|
||||
tpd->named = false;
|
||||
str = nvme_mi_endpoint_desc(ep);
|
||||
assert(str);
|
||||
assert(!strcmp(str, "test-mi endpoint"));
|
||||
free(str);
|
||||
|
||||
tpd->named = true;
|
||||
str = nvme_mi_endpoint_desc(ep);
|
||||
assert(str);
|
||||
assert(!strcmp(str, "test-mi: test endpoint 0x74657374"));
|
||||
free(str);
|
||||
}
|
||||
|
||||
/* test: invalid crc */
|
||||
static int test_invalid_crc_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
resp->mic = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_invalid_crc(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_invalid_crc_cb, NULL);
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: test that the controller list populates the endpoint's list of
|
||||
* controllers */
|
||||
static int test_scan_ctrl_list_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
__u8 ror, mt, *hdr, *buf;
|
||||
|
||||
assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
|
||||
|
||||
ror = req->hdr->nmp >> 7;
|
||||
mt = req->hdr->nmp >> 3 & 0x7;
|
||||
assert(ror == NVME_MI_ROR_REQ);
|
||||
assert(mt == NVME_MI_MT_MI);
|
||||
|
||||
/* do we have enough for a mi header? */
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
|
||||
|
||||
/* inspect response as raw bytes */
|
||||
hdr = (__u8 *)req->hdr;
|
||||
assert(hdr[4] == nvme_mi_mi_opcode_mi_data_read);
|
||||
assert(hdr[11] == nvme_mi_dtyp_ctrl_list);
|
||||
|
||||
/* create basic response */
|
||||
assert(resp->hdr_len >= sizeof(struct nvme_mi_mi_resp_hdr));
|
||||
assert(resp->data_len >= 4);
|
||||
|
||||
hdr = (__u8 *)resp->hdr;
|
||||
hdr[4] = 0; /* status */
|
||||
|
||||
buf = (__u8 *)resp->data;
|
||||
memset(buf, 0, resp->data_len);
|
||||
buf[0] = 3; buf[1] = 0; /* num controllers */
|
||||
buf[2] = 1; buf[3] = 0; /* id 1 */
|
||||
buf[4] = 4; buf[5] = 0; /* id 4 */
|
||||
buf[6] = 5; buf[7] = 0; /* id 5 */
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_scan_ctrl_list(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_ctrl *ctrl;
|
||||
|
||||
ep->controllers_scanned = false;
|
||||
|
||||
test_set_transport_callback(ep, test_scan_ctrl_list_cb, NULL);
|
||||
|
||||
nvme_mi_scan_ep(ep, false);
|
||||
|
||||
ctrl = nvme_mi_first_ctrl(ep);
|
||||
assert(ctrl);
|
||||
assert(ctrl->id == 1);
|
||||
|
||||
ctrl = nvme_mi_next_ctrl(ep, ctrl);
|
||||
assert(ctrl);
|
||||
assert(ctrl->id == 4);
|
||||
|
||||
ctrl = nvme_mi_next_ctrl(ep, ctrl);
|
||||
assert(ctrl);
|
||||
assert(ctrl->id == 5);
|
||||
|
||||
ctrl = nvme_mi_next_ctrl(ep, ctrl);
|
||||
assert(ctrl == NULL);
|
||||
}
|
||||
|
||||
/* test: simple NVMe admin request/response */
|
||||
static int test_admin_id_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
__u8 ror, mt, *hdr;
|
||||
__u32 dlen, cdw10;
|
||||
__u16 ctrl_id;
|
||||
__u8 flags;
|
||||
|
||||
assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
|
||||
|
||||
ror = req->hdr->nmp >> 7;
|
||||
mt = req->hdr->nmp >> 3 & 0x7;
|
||||
assert(ror == NVME_MI_ROR_REQ);
|
||||
assert(mt == NVME_MI_MT_ADMIN);
|
||||
|
||||
/* do we have enough for a mi header? */
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
|
||||
|
||||
/* inspect response as raw bytes */
|
||||
hdr = (__u8 *)req->hdr;
|
||||
assert(hdr[4] == nvme_admin_identify);
|
||||
flags = hdr[5];
|
||||
|
||||
ctrl_id = hdr[7] << 8 | hdr[6];
|
||||
assert(ctrl_id == 0x5); /* controller id */
|
||||
|
||||
/* we requested a full id; if we've set the length flag,
|
||||
* ensure the length matches */
|
||||
dlen = hdr[35] << 24 | hdr[34] << 16 | hdr[33] << 8 | hdr[32];
|
||||
if (flags & 0x1) {
|
||||
assert(dlen == sizeof(struct nvme_id_ctrl));
|
||||
}
|
||||
assert(!(flags & 0x2));
|
||||
|
||||
/* CNS value of 1 in cdw10 field */
|
||||
cdw10 = hdr[47] << 24 | hdr[46] << 16 | hdr[45] << 8 | hdr[44];
|
||||
assert(cdw10 == 0x1);
|
||||
|
||||
/* create valid (but somewhat empty) response */
|
||||
hdr = (__u8 *)resp->hdr;
|
||||
hdr[4] = 0x00; /* status: success */
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_admin_id(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_admin_id_cb, NULL);
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 5);
|
||||
assert(ctrl);
|
||||
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
/* test: simple NVMe error response */
|
||||
static int test_admin_err_resp_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
__u8 ror, mt, *hdr;
|
||||
|
||||
assert(req->hdr->type == NVME_MI_MSGTYPE_NVME);
|
||||
|
||||
ror = req->hdr->nmp >> 7;
|
||||
mt = req->hdr->nmp >> 3 & 0x7;
|
||||
assert(ror == NVME_MI_ROR_REQ);
|
||||
assert(mt == NVME_MI_MT_ADMIN);
|
||||
|
||||
/* do we have enough for a mi header? */
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_admin_req_hdr));
|
||||
|
||||
/* inspect response as raw bytes */
|
||||
hdr = (__u8 *)req->hdr;
|
||||
assert(hdr[4] == nvme_admin_identify);
|
||||
|
||||
/* we need at least 8 bytes for error information */
|
||||
assert(resp->hdr_len >= 8);
|
||||
|
||||
/* create error response */
|
||||
hdr = (__u8 *)resp->hdr;
|
||||
hdr[4] = 0x02; /* status: internal error */
|
||||
hdr[5] = 0;
|
||||
hdr[6] = 0;
|
||||
hdr[7] = 0;
|
||||
resp->hdr_len = 8;
|
||||
resp->data_len = 0;
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_admin_err_resp(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_id_ctrl id;
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_admin_err_resp_cb, NULL);
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
assert(ctrl);
|
||||
|
||||
rc = nvme_mi_admin_identify_ctrl(ctrl, &id);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* invalid Admin command transfers */
|
||||
static int test_admin_invalid_formats_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
/* none of the tests should result in message transfer */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_admin_invalid_formats(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_admin_resp_hdr resp = { 0 };
|
||||
struct nvme_mi_admin_req_hdr req = { 0 };
|
||||
nvme_mi_ctrl_t ctrl;
|
||||
size_t len;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_admin_invalid_formats_cb, NULL);
|
||||
|
||||
ctrl = nvme_mi_init_ctrl(ep, 1);
|
||||
assert(ctrl);
|
||||
|
||||
/* unaligned req size */
|
||||
len = 0;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 1, &resp, 0, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* unaligned resp size */
|
||||
len = 1;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 0, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* unaligned resp offset */
|
||||
len = 4;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 1, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* resp too large */
|
||||
len = 4096 + 4;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 0, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* resp offset too large */
|
||||
len = 4;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, (off_t)1 << 32, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* resp offset with no len */
|
||||
len = 0;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 0, &resp, 4, &len);
|
||||
assert(rc != 0);
|
||||
|
||||
/* req and resp payloads */
|
||||
len = 4;
|
||||
rc = nvme_mi_admin_xfer(ctrl, &req, 4, &resp, 0, &len);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: header length too small */
|
||||
static int test_resp_hdr_small_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
resp->hdr_len = 2;
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_resp_hdr_small(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_resp_hdr_small_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: respond with a request message */
|
||||
static int test_resp_req_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
resp->hdr->nmp &= ~(NVME_MI_ROR_RSP << 7);
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_resp_req(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_resp_req_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: invalid MCTP type in response */
|
||||
static int test_resp_invalid_type_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
resp->hdr->type = 0x3;
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_resp_invalid_type(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_resp_invalid_type_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: response with mis-matching command slot */
|
||||
static int test_resp_csi_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
resp->hdr->nmp ^= 0x1;
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_resp_csi(nvme_mi_ep_t ep)
|
||||
{
|
||||
struct nvme_mi_read_nvm_ss_info ss_info;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_resp_csi_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
|
||||
assert(rc != 0);
|
||||
}
|
||||
|
||||
/* test: config get MTU request & response layout, ensure we're handling
|
||||
* endianness in the 3-byte NMRESP field correctly */
|
||||
static int test_mi_config_get_mtu_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
struct nvme_mi_mi_resp_hdr *mi_resp;
|
||||
uint8_t *buf;
|
||||
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
|
||||
assert(req->data_len == 0);
|
||||
|
||||
/* validate req as raw bytes */
|
||||
buf = (void *)req->hdr;
|
||||
assert(buf[4] == nvme_mi_mi_opcode_configuration_get);
|
||||
/* dword 0: port and config id */
|
||||
assert(buf[11] == 0x5);
|
||||
assert(buf[8] == NVME_MI_CONFIG_MCTP_MTU);
|
||||
|
||||
/* set MTU in response */
|
||||
mi_resp = (void *)resp->hdr;
|
||||
mi_resp->nmresp[1] = 0x12;
|
||||
mi_resp->nmresp[0] = 0x34;
|
||||
resp->hdr_len = sizeof(*mi_resp);
|
||||
resp->data_len = 0;
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_mi_config_get_mtu(nvme_mi_ep_t ep)
|
||||
{
|
||||
uint16_t mtu;
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_mi_config_get_mtu_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_config_get_mctp_mtu(ep, 5, &mtu);
|
||||
assert(rc == 0);
|
||||
assert(mtu == 0x1234);
|
||||
}
|
||||
|
||||
/* test: config set SMBus freq, both valid and invalid */
|
||||
static int test_mi_config_set_freq_cb(struct nvme_mi_ep *ep,
|
||||
struct nvme_mi_req *req,
|
||||
struct nvme_mi_resp *resp,
|
||||
void *data)
|
||||
{
|
||||
struct nvme_mi_mi_resp_hdr *mi_resp;
|
||||
uint8_t *buf;
|
||||
|
||||
assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr));
|
||||
assert(req->data_len == 0);
|
||||
|
||||
/* validate req as raw bytes */
|
||||
buf = (void *)req->hdr;
|
||||
assert(buf[4] == nvme_mi_mi_opcode_configuration_set);
|
||||
/* dword 0: port and config id */
|
||||
assert(buf[11] == 0x5);
|
||||
assert(buf[8] == NVME_MI_CONFIG_SMBUS_FREQ);
|
||||
|
||||
mi_resp = (void *)resp->hdr;
|
||||
resp->hdr_len = sizeof(*mi_resp);
|
||||
resp->data_len = 0;
|
||||
|
||||
/* accept 100 & 400, reject others */
|
||||
switch (buf[9]) {
|
||||
case NVME_MI_CONFIG_SMBUS_FREQ_100kHz:
|
||||
case NVME_MI_CONFIG_SMBUS_FREQ_400kHz:
|
||||
mi_resp->status = 0;
|
||||
break;
|
||||
case NVME_MI_CONFIG_SMBUS_FREQ_1MHz:
|
||||
default:
|
||||
mi_resp->status = 0x4;
|
||||
break;
|
||||
}
|
||||
|
||||
test_transport_resp_calc_mic(resp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_mi_config_set_freq(nvme_mi_ep_t ep)
|
||||
{
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_config_set_smbus_freq(ep, 5,
|
||||
NVME_MI_CONFIG_SMBUS_FREQ_100kHz);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static void test_mi_config_set_freq_invalid(nvme_mi_ep_t ep)
|
||||
{
|
||||
int rc;
|
||||
|
||||
test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL);
|
||||
|
||||
rc = nvme_mi_mi_config_set_smbus_freq(ep, 5,
|
||||
NVME_MI_CONFIG_SMBUS_FREQ_1MHz);
|
||||
assert(rc == 4);
|
||||
}
|
||||
|
||||
#define DEFINE_TEST(name) { #name, test_ ## name }
|
||||
struct test {
|
||||
const char *name;
|
||||
void (*fn)(nvme_mi_ep_t);
|
||||
} tests[] = {
|
||||
DEFINE_TEST(endpoint_lifetime),
|
||||
DEFINE_TEST(ctrl_lifetime),
|
||||
DEFINE_TEST(read_mi_data),
|
||||
DEFINE_TEST(transport_fail),
|
||||
DEFINE_TEST(transport_describe),
|
||||
DEFINE_TEST(scan_ctrl_list),
|
||||
DEFINE_TEST(invalid_crc),
|
||||
DEFINE_TEST(admin_id),
|
||||
DEFINE_TEST(admin_err_resp),
|
||||
DEFINE_TEST(admin_invalid_formats),
|
||||
DEFINE_TEST(resp_req),
|
||||
DEFINE_TEST(resp_hdr_small),
|
||||
DEFINE_TEST(resp_invalid_type),
|
||||
DEFINE_TEST(resp_csi),
|
||||
DEFINE_TEST(mi_config_get_mtu),
|
||||
DEFINE_TEST(mi_config_set_freq),
|
||||
DEFINE_TEST(mi_config_set_freq_invalid),
|
||||
};
|
||||
|
||||
static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep)
|
||||
{
|
||||
printf("Running test %s...", test->name);
|
||||
fflush(stdout);
|
||||
test->fn(ep);
|
||||
/* tests will assert on failure; if we're here, we're OK */
|
||||
printf(" OK\n");
|
||||
test_print_log_buf(logfd);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
nvme_root_t root;
|
||||
nvme_mi_ep_t ep;
|
||||
unsigned int i;
|
||||
FILE *fd;
|
||||
|
||||
fd = test_setup_log();
|
||||
|
||||
root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
|
||||
assert(root);
|
||||
|
||||
ep = nvme_mi_open_test(root);
|
||||
assert(ep);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
run_test(&tests[i], fd, ep);
|
||||
}
|
||||
|
||||
nvme_mi_close(ep);
|
||||
nvme_mi_free_root(root);
|
||||
|
||||
test_close_log(fd);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -11,6 +11,8 @@
|
|||
* for your pci device found in /sys/class/nvme/nvmeX/device/resource0
|
||||
*/
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <libnvme.h>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include <uuid.h>
|
||||
#include <libnvme.h>
|
||||
|
||||
#include <ccan/endian/endian.h>
|
||||
|
|
68
test/utils.c
Normal file
68
test/utils.c
Normal file
|
@ -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");
|
||||
}
|
||||
|
20
test/utils.h
Normal file
20
test/utils.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*
|
||||
* This file is part of libnvme.
|
||||
* Copyright (c) 2022 Code Construct
|
||||
*
|
||||
* Common test utilities for libnvme tests. These have quite strict error
|
||||
* handling, so the general pattern is to abort/exit on error.
|
||||
*/
|
||||
|
||||
#ifndef _TEST_UTILS_H
|
||||
#define _TEST_UTILS_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
FILE *test_setup_log(void);
|
||||
void test_print_log_buf(FILE *logfd);
|
||||
void test_close_log(FILE *fd);
|
||||
|
||||
#endif /* _TEST_UTILS_H */
|
||||
|
|
@ -40,6 +40,7 @@ static void show_zns_properties(nvme_ns_t n)
|
|||
|
||||
if (nvme_zns_identify_ctrl(nvme_ns_get_fd(n), &zns_ctrl)) {
|
||||
fprintf(stderr, "failed to identify zns ctrl\n");;
|
||||
free(zr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -51,6 +52,7 @@ static void show_zns_properties(nvme_ns_t n)
|
|||
NVME_DEFAULT_IOCTL_TIMEOUT, &result)) {
|
||||
fprintf(stderr, "failed to report zones, result %x\n",
|
||||
le32_to_cpu(result));
|
||||
free(zr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue