1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16 /*
17 * Common functions for the various ioctl tests that we're using.
18 */
19
20 #include <err.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <libdevinfo.h>
24 #include <unistd.h>
25 #include <libproc.h>
26
27 #include "nvme_ioctl_util.h"
28
29 /*
30 * Cached copies of devinfo nodes to speed up subsequent lookups.
31 */
32 static di_node_t nvme_test_root;
33 static di_node_t nvme_test_dev;
34
35 /*
36 * Lock ioctl template structures. These are all non-blocking locks because we
37 * don't want the tests to hang in error.
38 */
39 const nvme_ioctl_lock_t nvme_test_ctrl_wrlock = {
40 .nil_ent = NVME_LOCK_E_CTRL,
41 .nil_level = NVME_LOCK_L_WRITE,
42 .nil_flags = NVME_LOCK_F_DONT_BLOCK
43 };
44
45 const nvme_ioctl_lock_t nvme_test_ctrl_rdlock = {
46 .nil_ent = NVME_LOCK_E_CTRL,
47 .nil_level = NVME_LOCK_L_READ,
48 .nil_flags = NVME_LOCK_F_DONT_BLOCK
49 };
50
51 const nvme_ioctl_lock_t nvme_test_ns_wrlock = {
52 .nil_common = { .nioc_nsid = 1 },
53 .nil_ent = NVME_LOCK_E_NS,
54 .nil_level = NVME_LOCK_L_WRITE,
55 .nil_flags = NVME_LOCK_F_DONT_BLOCK
56 };
57
58 const nvme_ioctl_lock_t nvme_test_ns_rdlock = {
59 .nil_common = { .nioc_nsid = 1 },
60 .nil_ent = NVME_LOCK_E_NS,
61 .nil_level = NVME_LOCK_L_READ,
62 .nil_flags = NVME_LOCK_F_DONT_BLOCK
63 };
64
65 const nvme_ioctl_unlock_t nvme_test_ctrl_unlock = {
66 .niu_ent = NVME_LOCK_E_CTRL
67 };
68
69 const nvme_ioctl_unlock_t nvme_test_ns_unlock = {
70 .niu_common = { .nioc_nsid = 1 },
71 .niu_ent = NVME_LOCK_E_NS
72 };
73
74 static int
nvme_ioctl_test_find_nsid(di_node_t di,uint32_t nsid,int oflag)75 nvme_ioctl_test_find_nsid(di_node_t di, uint32_t nsid, int oflag)
76 {
77 int fd;
78 const char *type;
79 char name[128], *mpath, path[PATH_MAX];
80 di_minor_t minor;
81
82 if (nsid == 0) {
83 type = DDI_NT_NVME_NEXUS;
84 (void) strlcpy(name, "devctl", sizeof (name));
85 } else {
86 type = DDI_NT_NVME_ATTACHMENT_POINT;
87 (void) snprintf(name, sizeof (name), "%u", nsid);
88 }
89
90 minor = DI_MINOR_NIL;
91 while ((minor = di_minor_next(di, minor)) != DI_MINOR_NIL) {
92 if (strcmp(di_minor_nodetype(minor), type) == 0 &&
93 strcmp(di_minor_name(minor), name) == 0) {
94 break;
95 }
96 }
97
98 if (minor == DI_MINOR_NIL) {
99 errx(EXIT_FAILURE, "failed to find minor for nsid %u on %s%d",
100 nsid, di_driver_name(di), di_instance(di));
101 }
102
103 mpath = di_devfs_minor_path(minor);
104 if (mpath == NULL) {
105 err(EXIT_FAILURE, "failed to get minor device path for nsid %u "
106 "on %s%d", nsid, di_driver_name(di), di_instance(di));
107 }
108
109 if (snprintf(path, sizeof (path), "/devices%s", mpath) >=
110 sizeof (path)) {
111 errx(EXIT_FAILURE, "failed to construct full /devices path for "
112 "%s: snprintf buffer would have overflowed", mpath);
113 }
114 di_devfs_path_free(mpath);
115
116 fd = open(path, oflag);
117 if (fd < 0) {
118 err(EXIT_FAILURE, "failed to open minor path %s", path);
119 }
120
121 return (fd);
122 }
123
124 /*
125 * The ioctl tests expect an NVMe device to be nominated for use to test
126 * against. Translate that device into an fd.
127 */
128 int
nvme_ioctl_test_get_fd_flags(uint32_t nsid,int oflag)129 nvme_ioctl_test_get_fd_flags(uint32_t nsid, int oflag)
130 {
131 const char *dev, *errstr;
132 long long ll;
133
134 if (nvme_test_dev != NULL) {
135 return (nvme_ioctl_test_find_nsid(nvme_test_dev, nsid, oflag));
136 }
137
138 dev = getenv(NVME_TEST_DEV_ENVVAR);
139 if (dev == NULL) {
140 errx(EXIT_FAILURE, "cannot run test, missing required NVMe "
141 "device, please set the %s environment variable",
142 NVME_TEST_DEV_ENVVAR);
143 }
144
145 if (strncmp("nvme", dev, 4) != 0) {
146 errx(EXIT_FAILURE, "%s environment variable device %s does "
147 "not begin with 'nvme'", NVME_TEST_DEV_ENVVAR, dev);
148 }
149
150 ll = strtonum(dev + 4, 0, INT32_MAX, &errstr);
151 if (errstr != NULL) {
152 errx(EXIT_FAILURE, "failed to parse %s environment variable "
153 "device %s instance: value is %s", NVME_TEST_DEV_ENVVAR,
154 dev, errstr);
155 }
156
157 if (nvme_test_root == NULL) {
158 nvme_test_root = di_init("/", DINFOCPYALL);
159 if (nvme_test_root == DI_NODE_NIL) {
160 err(EXIT_FAILURE, "failed to initialize libdevinfo");
161 }
162 }
163
164 for (di_node_t di = di_drv_first_node("nvme", nvme_test_root);
165 di != DI_NODE_NIL; di = di_drv_next_node(di)) {
166 if (di_instance(di) == (int)ll) {
167 nvme_test_dev = di;
168 return (nvme_ioctl_test_find_nsid(di, nsid, oflag));
169 }
170 }
171
172 errx(EXIT_FAILURE, "failed to find %s environment variable device %s: "
173 "cannot run test", NVME_TEST_DEV_ENVVAR, dev);
174 }
175
176 int
nvme_ioctl_test_get_fd(uint32_t nsid)177 nvme_ioctl_test_get_fd(uint32_t nsid)
178 {
179 return (nvme_ioctl_test_get_fd_flags(nsid, O_RDWR));
180 }
181
182 /*
183 * This is a wrapper that requires we successfully lock something.
184 */
185 void
nvme_ioctl_test_lock(int fd,const nvme_ioctl_lock_t * lockp)186 nvme_ioctl_test_lock(int fd, const nvme_ioctl_lock_t *lockp)
187 {
188 nvme_ioctl_lock_t lock = *lockp;
189 const char *targ = lockp->nil_ent == NVME_LOCK_E_CTRL ?
190 "controller" : "namespace";
191 const char *level = lockp->nil_level == NVME_LOCK_L_READ ?
192 "read" : "write";
193
194 if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) {
195 err(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due "
196 "to failure to issue %s %s lock ioctl", targ, level);
197 } else if (lock.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) {
198 errx(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due "
199 "to failure to obtain %s %s lock, got 0x%x", targ, level,
200 lock.nil_common.nioc_drv_err);
201 }
202 }
203
204 /*
205 * Determine if a thread is blocked in our locking ioctl. We use proc_sysname()
206 * so we can avoid encoding the system call number of the ioctl into the test
207 * directly.
208 */
209 bool
nvme_ioctl_test_thr_blocked(thread_t thr)210 nvme_ioctl_test_thr_blocked(thread_t thr)
211 {
212 lwpstatus_t lwp;
213 char name[SYS2STR_MAX];
214
215 if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) {
216 err(EXIT_FAILURE, "TEST FAILED: unable to continue test "
217 "execution as we failed to retrieve the lwpsinfo_t data "
218 "for thread 0x%x", thr);
219 }
220
221 if ((lwp.pr_flags & PR_ASLEEP) == 0)
222 return (false);
223
224 if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == NULL)
225 return (false);
226
227 return (strcmp(name, "ioctl") == 0);
228 }
229
230 const char *
nvme_ioctl_test_cmdstr(int cmd)231 nvme_ioctl_test_cmdstr(int cmd)
232 {
233 switch (cmd) {
234 case NVME_IOC_CTRL_INFO:
235 return ("NVME_IOC_CTRL_INFO");
236 case NVME_IOC_IDENTIFY:
237 return ("NVME_IOC_IDENTIFY");
238 case NVME_IOC_GET_LOGPAGE:
239 return ("NVME_IOC_GET_LOGPAGE");
240 case NVME_IOC_GET_FEATURE:
241 return ("NVME_IOC_GET_FEATURE");
242 case NVME_IOC_FORMAT:
243 return ("NVME_IOC_FORMAT");
244 case NVME_IOC_BD_DETACH:
245 return ("NVME_IOC_BD_DETACH");
246 case NVME_IOC_BD_ATTACH:
247 return ("NVME_IOC_BD_ATTACH");
248 case NVME_IOC_FIRMWARE_DOWNLOAD:
249 return ("NVME_IOC_FIRMWARE_DOWNLOAD");
250 case NVME_IOC_FIRMWARE_COMMIT:
251 return ("NVME_IOC_FIRMWARE_COMMIT");
252 case NVME_IOC_PASSTHRU:
253 return ("NVME_IOC_PASSTHRU");
254 case NVME_IOC_NS_INFO:
255 return ("NVME_IOC_NS_INFO");
256 case NVME_IOC_LOCK:
257 return ("NVME_IOC_LOCK");
258 case NVME_IOC_UNLOCK:
259 return ("NVME_IOC_UNLOCK");
260 case NVME_IOC_CTRL_ATTACH:
261 return ("NVME_IOC_CTRL_ATTACH");
262 case NVME_IOC_CTRL_DETACH:
263 return ("NVME_IOC_CTRL_DETACH");
264 case NVME_IOC_NS_CREATE:
265 return ("NVME_IOC_NS_CREATE");
266 case NVME_IOC_NS_DELETE:
267 return ("NVME_IOC_NS_DELETE");
268 default:
269 return ("unknown");
270 }
271 }
272