Skip to content

Commit 9f1ae9c

Browse files
committed
test: Add initial tests for mi-mctp layer
This change adds a new test module, for the MCTP transport of the MI layer. To do this, we hook into the socket, sendmsg and recvmsg calls from the core mi-mctp code, allowing the test to provide a mock communication with the kernel MCTP layer. On top of this, we write a few simple test cases for the socket error resonses, which we'll extend in future changes. Signed-off-by: Jeremy Kerr <[email protected]>
1 parent 8f917d6 commit 9f1ae9c

4 files changed

Lines changed: 285 additions & 3 deletions

File tree

src/nvme/mi-mctp.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ struct nvme_mi_transport_mctp {
8080
int sd;
8181
};
8282

83+
static struct __mi_mctp_socket_ops ops = {
84+
socket,
85+
sendmsg,
86+
recvmsg,
87+
};
88+
89+
void __nvme_mi_mctp_set_ops(const struct __mi_mctp_socket_ops *newops)
90+
{
91+
ops = *newops;
92+
}
93+
8394
#define MCTP_DBUS_PATH "/xyz/openbmc_project/mctp"
8495
#define MCTP_DBUS_IFACE "xyz.openbmc_project.MCTP"
8596
#define MCTP_DBUS_IFACE_ENDPOINT "xyz.openbmc_project.MCTP.Endpoint"
@@ -133,7 +144,7 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
133144
req_msg.msg_iov = req_iov;
134145
req_msg.msg_iovlen = i;
135146

136-
len = sendmsg(mctp->sd, &req_msg, 0);
147+
len = ops.sendmsg(mctp->sd, &req_msg, 0);
137148
if (len < 0) {
138149
nvme_msg(ep->root, LOG_ERR,
139150
"Failure sending MCTP message: %m\n");
@@ -159,7 +170,7 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
159170
resp_msg.msg_iov = resp_iov;
160171
resp_msg.msg_iovlen = 2;
161172

162-
len = recvmsg(mctp->sd, &resp_msg, 0);
173+
len = ops.recvmsg(mctp->sd, &resp_msg, 0);
163174

164175
if (len < 0) {
165176
nvme_msg(ep->root, LOG_ERR,
@@ -242,7 +253,7 @@ nvme_mi_ep_t nvme_mi_open_mctp(nvme_root_t root, unsigned int netid, __u8 eid)
242253
mctp->net = netid;
243254
mctp->eid = eid;
244255

245-
mctp->sd = socket(AF_MCTP, SOCK_DGRAM, 0);
256+
mctp->sd = ops.socket(AF_MCTP, SOCK_DGRAM, 0);
246257
if (mctp->sd < 0)
247258
goto err_free_ep;
248259

src/nvme/private.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define _LIBNVME_PRIVATE_H
1111

1212
#include <ccan/list/list.h>
13+
#include <sys/socket.h>
1314

1415
#include "fabrics.h"
1516
#include "mi.h"
@@ -204,4 +205,14 @@ struct nvme_mi_ep *nvme_mi_init_ep(struct nvme_root *root);
204205
/* for tests, we need to calculate the correct MICs */
205206
__u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
206207

208+
/* we have a facility to mock MCTP socket operations in the mi-mctp transport,
209+
* using this ops type. This should only be used for test, and isn't exposed
210+
* in the shared lib */;
211+
struct __mi_mctp_socket_ops {
212+
int (*socket)(int, int, int);
213+
ssize_t (*sendmsg)(int, const struct msghdr *, int);
214+
ssize_t (*recvmsg)(int, struct msghdr *, int);
215+
};
216+
void __nvme_mi_mctp_set_ops(const struct __mi_mctp_socket_ops *newops);
217+
207218
#endif /* _LIBNVME_PRIVATE_H */

test/meson.build

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ mi = executable(
4747
)
4848

4949
test('mi', mi)
50+
51+
mi_mctp = executable(
52+
'test-mi-mctp',
53+
['mi-mctp.c', 'utils.c'],
54+
dependencies: libnvme_mi_test_dep,
55+
include_directories: [incdir, internal_incdir],
56+
)
57+
58+
test('mi-mctp', mi_mctp)

test/mi-mctp.c

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// SPDX-License-Identifier: LGPL-2.1-or-later
2+
/**
3+
* This file is part of libnvme.
4+
* Copyright (c) 2022 Code Construct
5+
*/
6+
7+
#undef NDEBUG
8+
#include <assert.h>
9+
#include <errno.h>
10+
#include <fcntl.h>
11+
#include <stdlib.h>
12+
#include <stdio.h>
13+
#include <string.h>
14+
#include <unistd.h>
15+
#include <sys/socket.h>
16+
17+
18+
#include <ccan/array_size/array_size.h>
19+
#include <ccan/endian/endian.h>
20+
21+
#include "libnvme-mi.h"
22+
#include "nvme/private.h"
23+
#include "utils.h"
24+
25+
/* 4096 byte max MCTP message, plus space for header data */
26+
#define MAX_BUFSIZ 8192
27+
28+
struct test_peer;
29+
30+
typedef int (*rx_test_fn)(struct test_peer *peer, void *buf, size_t len);
31+
32+
/* Our fake MCTP "peer".
33+
*
34+
* The terms TX (transmit) and RX (receive) are from the perspective of
35+
* the NVMe device. TX is device-to-libnvme, RX is libnvme-to-device.
36+
*
37+
* The RX and TX buffers are linear versions of the data sent and received by
38+
* libnvme-mi, and *include* the MCTP message type byte (even though it's
39+
* omitted in the sendmsg/recvmsg interface), so that the buffer inspection
40+
* in the tests can exactly match the NVMe-MI spec packet diagrams.
41+
*/
42+
static struct test_peer {
43+
/* rx (sendmsg) data sent from libnvme, and return value */
44+
unsigned char rx_buf[MAX_BUFSIZ];
45+
size_t rx_buf_len;
46+
ssize_t rx_rc; /* if zero, return the sendmsg len */
47+
int rx_errno;
48+
49+
/* tx (recvmsg) data to be received by libnvme and return value */
50+
unsigned char tx_buf[MAX_BUFSIZ];
51+
size_t tx_buf_len;
52+
ssize_t tx_rc; /* if zero, return the recvmsg len */
53+
int tx_errno;
54+
55+
/* Optional, called after RX, may set tx_buf according to request.
56+
* Return value stored in rx_res, may be used by test */
57+
rx_test_fn rx_fn;
58+
void *rx_data;
59+
int rx_res;
60+
61+
/* store sd from socket() setup */
62+
int sd;
63+
} test_peer;
64+
65+
/* ensure tests start from a standard state */
66+
void reset_test_peer(void)
67+
{
68+
int tmp = test_peer.sd;
69+
memset(&test_peer, 0, sizeof(test_peer));
70+
test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
71+
test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
72+
test_peer.sd = tmp;
73+
}
74+
75+
/* calculate MIC of peer-to-libnvme data, expand buf by 4 bytes and insert
76+
* the new MIC */
77+
static void test_set_tx_mic(struct test_peer *peer)
78+
{
79+
extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
80+
__u32 crc = 0xffffffff;
81+
82+
assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ);
83+
84+
crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len);
85+
*(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc);
86+
peer->tx_buf_len += sizeof(crc);
87+
}
88+
89+
int __wrap_socket(int family, int type, int protocol)
90+
{
91+
/* we do an open here to give the mi-mctp code something to close() */
92+
test_peer.sd = open("/dev/null", 0);
93+
return test_peer.sd;
94+
}
95+
96+
ssize_t __wrap_sendmsg(int sd, const struct msghdr *hdr, int flags)
97+
{
98+
size_t i, pos;
99+
100+
assert(sd == test_peer.sd);
101+
102+
test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
103+
104+
/* gather iovec into buf */
105+
for (i = 0, pos = 1; i < hdr->msg_iovlen; i++) {
106+
struct iovec *iov = &hdr->msg_iov[i];
107+
108+
assert(pos + iov->iov_len < MAX_BUFSIZ - 1);
109+
memcpy(test_peer.rx_buf + pos, iov->iov_base, iov->iov_len);
110+
pos += iov->iov_len;
111+
}
112+
113+
test_peer.rx_buf_len = pos;
114+
115+
if (test_peer.rx_fn)
116+
test_peer.rx_res = test_peer.rx_fn(&test_peer,
117+
test_peer.rx_buf,
118+
test_peer.rx_buf_len);
119+
120+
errno = test_peer.rx_errno;
121+
122+
return test_peer.rx_rc ?: (pos - 1);
123+
}
124+
125+
ssize_t __wrap_recvmsg(int sd, struct msghdr *hdr, int flags)
126+
{
127+
size_t i, pos, len;
128+
129+
assert(sd == test_peer.sd);
130+
131+
/* scatter buf into iovec */
132+
for (i = 0, pos = 1; i < hdr->msg_iovlen && pos < test_peer.tx_buf_len;
133+
i++) {
134+
struct iovec *iov = &hdr->msg_iov[i];
135+
136+
len = iov->iov_len;
137+
if (len > test_peer.tx_buf_len - pos)
138+
len = test_peer.tx_buf_len - pos;
139+
140+
memcpy(iov->iov_base, test_peer.tx_buf + pos, len);
141+
pos += len;
142+
}
143+
144+
errno = test_peer.tx_errno;
145+
146+
return test_peer.tx_rc ?: (pos - 1);
147+
}
148+
149+
static struct __mi_mctp_socket_ops ops = {
150+
__wrap_socket,
151+
__wrap_sendmsg,
152+
__wrap_recvmsg,
153+
};
154+
155+
/* tests */
156+
static void test_rx_err(nvme_mi_ep_t ep, struct test_peer *peer)
157+
{
158+
struct nvme_mi_read_nvm_ss_info ss_info;
159+
int rc;
160+
161+
peer->rx_rc = -1;
162+
163+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
164+
assert(rc != 0);
165+
}
166+
167+
static void test_tx_err(nvme_mi_ep_t ep, struct test_peer *peer)
168+
{
169+
struct nvme_mi_read_nvm_ss_info ss_info;
170+
int rc;
171+
172+
peer->tx_rc = -1;
173+
174+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
175+
assert(rc != 0);
176+
}
177+
178+
static void test_tx_short(nvme_mi_ep_t ep, struct test_peer *peer)
179+
{
180+
struct nvme_mi_read_nvm_ss_info ss_info;
181+
int rc;
182+
183+
peer->tx_buf_len = 11;
184+
185+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
186+
assert(rc != 0);
187+
}
188+
189+
static void test_read_mi_data(nvme_mi_ep_t ep, struct test_peer *peer)
190+
{
191+
struct nvme_mi_read_nvm_ss_info ss_info;
192+
int rc;
193+
194+
/* empty response data, but with correct MIC */
195+
peer->tx_buf_len = 8 + 32;
196+
test_set_tx_mic(peer);
197+
198+
rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
199+
assert(rc == 0);
200+
}
201+
202+
#define DEFINE_TEST(name) { #name, test_ ## name }
203+
struct test {
204+
const char *name;
205+
void (*fn)(nvme_mi_ep_t, struct test_peer *);
206+
} tests[] = {
207+
DEFINE_TEST(rx_err),
208+
DEFINE_TEST(tx_err),
209+
DEFINE_TEST(tx_short),
210+
DEFINE_TEST(read_mi_data),
211+
};
212+
213+
static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep,
214+
struct test_peer *peer)
215+
{
216+
printf("Running test %s...", test->name);
217+
fflush(stdout);
218+
test->fn(ep, peer);
219+
printf(" OK\n");
220+
test_print_log_buf(logfd);
221+
}
222+
223+
int main(void)
224+
{
225+
nvme_root_t root;
226+
nvme_mi_ep_t ep;
227+
unsigned int i;
228+
FILE *fd;
229+
230+
fd = test_setup_log();
231+
232+
__nvme_mi_mctp_set_ops(&ops);
233+
234+
root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
235+
assert(root);
236+
237+
ep = nvme_mi_open_mctp(root, 0, 0);
238+
assert(ep);
239+
240+
for (i = 0; i < ARRAY_SIZE(tests); i++) {
241+
reset_test_peer();
242+
run_test(&tests[i], fd, ep, &test_peer);
243+
}
244+
245+
nvme_mi_close(ep);
246+
nvme_mi_free_root(root);
247+
248+
test_close_log(fd);
249+
250+
return EXIT_SUCCESS;
251+
}

0 commit comments

Comments
 (0)