xref: /illumos-gate/usr/src/test/nvme-tests/tests/libnvme/need-write-lock.c (revision f5f0964ce91892f7482efc86903b0ec7c7b6ba66)
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  * Various destructive options require a write lock. These include:
18  *
19  *  - namespace creation and deletion
20  *  - attaching or detaching a controller to a namespace
21  *  - manipulating blkdev state
22  *  - formatting a namespace
23  *
24  * While firmware operations also require this, that is harder to test in this
25  * case as we don't have valid firmware files or want to manipulate the device.
26  * We check that operations fail in sevearl different situations:
27  *
28  * 1) With no locks held.
29  * 2) With a namespace read lock.
30  * 3) With a controller read lock.
31  * 4) With a namespace write lock (note, some items succeed here).
32  *
33  * This test starts from the device-empty profile.
34  */
35 
36 #include <err.h>
37 #include <stdlib.h>
38 #include <sys/sysmacros.h>
39 #include "libnvme_test_common.h"
40 
41 #define	LOCK_NSID	1
42 
43 static uint32_t lock_lbaf;
44 
45 typedef enum {
46 	LOCK_NONE,
47 	LOCK_NS_RD,
48 	LOCK_NS_WR,
49 	LOCK_CTRL_RD,
50 	LOCK_MAX
51 } lock_type_t;
52 
53 typedef struct write_test {
54 	const char *wt_desc;
55 	nvme_err_t wt_errs[LOCK_MAX];
56 	nvme_ns_disc_level_t wt_disc;
57 	bool (*wt_func)(nvme_ctrl_t *, const char *, nvme_err_t);
58 } write_test_t;
59 
60 static bool
write_lock_ns_create(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)61 write_lock_ns_create(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
62 {
63 	uint64_t create_size = NVME_TEST_NS_SIZE / NVME_TEST_LBA_SIZE;
64 	uint32_t nsid;
65 	nvme_err_t act;
66 
67 	if (!libnvme_test_ns_create(ctrl, create_size, lock_lbaf, &nsid,
68 	    &act)) {
69 		warnx("TEST FAILED: failed to initialize namespace create"
70 		    "request in lock %s iteration", desc);
71 		return (false);
72 	} else if (act != exp) {
73 		warnx("TEST FAILED: namespace create with %s returned %s "
74 		    "(0x%x), but expected %s (0x%x)", desc,
75 		    nvme_ctrl_errtostr(ctrl, act), act,
76 		    nvme_ctrl_errtostr(ctrl, exp), exp);
77 		return (false);
78 	}
79 
80 	(void) printf("TEST PASSED: namespace delete with %s returned %s\n",
81 	    desc, nvme_ctrl_errtostr(ctrl, act));
82 
83 	return (true);
84 }
85 
86 static bool
write_lock_ns_delete(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)87 write_lock_ns_delete(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
88 {
89 	nvme_err_t act;
90 
91 	if (!libnvme_test_ns_delete(ctrl, LOCK_NSID, &act)) {
92 		warnx("TEST FAILED: failed to initialize namespace delete "
93 		    "request in lock %s iteration", desc);
94 		return (false);
95 	} else if (act != exp) {
96 		warnx("TEST FAILED: namespace delete with %s returned %s "
97 		    "(0x%x), but expected %s (0x%x)", desc,
98 		    nvme_ctrl_errtostr(ctrl, act), act,
99 		    nvme_ctrl_errtostr(ctrl, exp), exp);
100 		return (false);
101 	}
102 
103 	(void) printf("TEST PASSED: namespace delete with %s returned %s\n",
104 	    desc, nvme_ctrl_errtostr(ctrl, act));
105 	return (true);
106 }
107 
108 static bool
write_lock_ctrl_detach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)109 write_lock_ctrl_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
110 {
111 	nvme_err_t act;
112 
113 	if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID,
114 	    NVME_NS_ATTACH_CTRL_DETACH, &act)) {
115 		warnx("TEST FAILED: failed to initialize controller detach "
116 		    "request in lock %s iteration", desc);
117 		return (false);
118 	} else if (act != exp) {
119 		warnx("TEST FAILED: controller detach with %s returned %s "
120 		    "(0x%x), but expected %s (0x%x)", desc,
121 		    nvme_ctrl_errtostr(ctrl, act), act,
122 		    nvme_ctrl_errtostr(ctrl, exp), exp);
123 		return (false);
124 	}
125 
126 	(void) printf("TEST PASSED: controller detach with %s returned %s\n",
127 	    desc, nvme_ctrl_errtostr(ctrl, act));
128 	return (true);
129 }
130 
131 static bool
write_lock_ctrl_attach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)132 write_lock_ctrl_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
133 {
134 	nvme_err_t act;
135 
136 	if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID,
137 	    NVME_NS_ATTACH_CTRL_ATTACH, &act)) {
138 		warnx("TEST FAILED: failed to initialize controller attach "
139 		    "request in lock %s iteration", desc);
140 		return (false);
141 	} else if (act != exp) {
142 		warnx("TEST FAILED: controller attach with %s returned %s "
143 		    "(0x%x), but expected %s (0x%x)", desc,
144 		    nvme_ctrl_errtostr(ctrl, act), act,
145 		    nvme_ctrl_errtostr(ctrl, exp), exp);
146 		return (false);
147 	}
148 
149 	(void) printf("TEST PASSED: controller attach with %s returned %s\n",
150 	    desc, nvme_ctrl_errtostr(ctrl, act));
151 	return (true);
152 }
153 
154 static bool
write_lock_blkdev_detach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)155 write_lock_blkdev_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
156 {
157 	nvme_err_t act;
158 
159 	if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, false, &act)) {
160 		warnx("TEST FAILED: failed to initialize blkdev detach "
161 		    "request in lock %s iteration", desc);
162 		return (false);
163 	} else if (act != exp) {
164 		warnx("TEST FAILED: blkdev detach with %s returned %s (0x%x), "
165 		    "but expected %s (0x%x)", desc,
166 		    nvme_ctrl_errtostr(ctrl, act), act,
167 		    nvme_ctrl_errtostr(ctrl, exp), exp);
168 		return (false);
169 	}
170 
171 	(void) printf("TEST PASSED: blkdev detach with %s returned %s\n", desc,
172 	    nvme_ctrl_errtostr(ctrl, act));
173 	return (true);
174 }
175 
176 static bool
write_lock_blkdev_attach(nvme_ctrl_t * ctrl,const char * desc,nvme_err_t exp)177 write_lock_blkdev_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp)
178 {
179 	nvme_err_t act;
180 
181 	if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, true, &act)) {
182 		warnx("TEST FAILED: failed to initialize blkdev attach "
183 		    "request in lock %s iteration", desc);
184 		return (false);
185 	} else if (act != exp) {
186 		warnx("TEST FAILED: blkdev attach with %s returned %s (0x%x), "
187 		    "but expected %s (0x%x)", desc,
188 		    nvme_ctrl_errtostr(ctrl, act), act,
189 		    nvme_ctrl_errtostr(ctrl, exp), exp);
190 		return (false);
191 	}
192 
193 	(void) printf("TEST PASSED: blkdev attach with %s returned %s\n", desc,
194 	    nvme_ctrl_errtostr(ctrl, act));
195 	return (true);
196 }
197 
198 static const write_test_t write_tests[] = { {
199 	.wt_desc = "namespace create",
200 	.wt_errs = {
201 		[LOCK_NONE] = NVME_ERR_NEED_CTRL_WRLOCK,
202 		[LOCK_NS_RD] = NVME_ERR_NEED_CTRL_WRLOCK,
203 		[LOCK_NS_WR] = NVME_ERR_NEED_CTRL_WRLOCK,
204 		[LOCK_CTRL_RD] = NVME_ERR_NEED_CTRL_WRLOCK
205 	},
206 	.wt_disc = NVME_NS_DISC_F_ALL,
207 	.wt_func = write_lock_ns_create
208 }, {
209 	.wt_desc = "namespace delete",
210 	.wt_errs = {
211 		[LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
212 		[LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
213 		[LOCK_NS_WR] = NVME_ERR_OK,
214 		[LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
215 	},
216 	.wt_disc = NVME_NS_DISC_F_ALLOCATED,
217 	.wt_func = write_lock_ns_delete
218 }, {
219 	.wt_desc = "controller attach",
220 	.wt_errs = {
221 		[LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
222 		[LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
223 		[LOCK_NS_WR] = NVME_ERR_OK,
224 		[LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
225 	},
226 	.wt_disc = NVME_NS_DISC_F_ALLOCATED,
227 	.wt_func = write_lock_ctrl_attach
228 }, {
229 	.wt_desc = "controller detach",
230 	.wt_errs = {
231 		[LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
232 		[LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
233 		[LOCK_NS_WR] = NVME_ERR_OK,
234 		[LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
235 	},
236 	.wt_disc = NVME_NS_DISC_F_ACTIVE,
237 	.wt_func = write_lock_ctrl_detach
238 }, {
239 	.wt_desc = "blkdev attach",
240 	.wt_errs = {
241 		[LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
242 		[LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
243 		[LOCK_NS_WR] = NVME_ERR_OK,
244 		[LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
245 	},
246 	.wt_disc = NVME_NS_DISC_F_NOT_IGNORED,
247 	.wt_func = write_lock_blkdev_attach
248 }, {
249 	.wt_desc = "blkdev detach",
250 	.wt_errs = {
251 		[LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK,
252 		[LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK,
253 		[LOCK_NS_WR] = NVME_ERR_OK,
254 		[LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK
255 	},
256 	.wt_disc = NVME_NS_DISC_F_BLKDEV,
257 	.wt_func = write_lock_blkdev_detach
258 } };
259 
260 typedef struct lock_info {
261 	const char *li_desc;
262 	bool (*li_lock_f)(nvme_ctrl_t *, nvme_ns_t *);
263 	void (*li_unlock_f)(nvme_ctrl_t *, nvme_ns_t *);
264 } lock_info_t;
265 
266 static bool
lock_none_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)267 lock_none_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
268 {
269 	return (true);
270 }
271 
272 static bool
lock_ns_read_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)273 lock_ns_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
274 {
275 	return (nvme_ns_lock(ns, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK));
276 }
277 
278 static bool
lock_ns_write_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)279 lock_ns_write_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
280 {
281 	return (nvme_ns_lock(ns, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK));
282 }
283 
284 static bool
lock_ctrl_read_lock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)285 lock_ctrl_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
286 {
287 	return (nvme_ctrl_lock(ctrl, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK));
288 }
289 
290 static void
lock_none_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)291 lock_none_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
292 {
293 }
294 
295 static void
lock_ns_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)296 lock_ns_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
297 {
298 	nvme_ns_unlock(ns);
299 }
300 
301 static void
lock_ctrl_unlock(nvme_ctrl_t * ctrl,nvme_ns_t * ns)302 lock_ctrl_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns)
303 {
304 	nvme_ctrl_unlock(ctrl);
305 }
306 
307 lock_info_t lock_info[LOCK_MAX] = {
308 	[LOCK_NONE] = {
309 		.li_desc = "no lock",
310 		.li_lock_f = lock_none_lock,
311 		.li_unlock_f = lock_none_unlock,
312 	},
313 	[LOCK_NS_RD] = {
314 		.li_desc = "namespace read lock",
315 		.li_lock_f = lock_ns_read_lock,
316 		.li_unlock_f = lock_ns_unlock,
317 	},
318 	[LOCK_NS_WR] = {
319 		.li_desc = "namespace write lock",
320 		.li_lock_f = lock_ns_write_lock,
321 		.li_unlock_f = lock_ns_unlock,
322 	},
323 	[LOCK_CTRL_RD] = {
324 		.li_desc = "controller read lock",
325 		.li_lock_f = lock_ctrl_read_lock,
326 		.li_unlock_f = lock_ctrl_unlock,
327 	}
328 
329 };
330 
331 static bool
write_test_one(const write_test_t * test,nvme_ctrl_t * ctrl,nvme_ns_t * ns)332 write_test_one(const write_test_t *test, nvme_ctrl_t *ctrl, nvme_ns_t *ns)
333 {
334 	bool ret = true;
335 
336 	for (lock_type_t i = LOCK_NONE; i < LOCK_MAX; i++) {
337 		if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE,
338 		    NVME_LOCK_F_DONT_BLOCK)) {
339 			libnvme_test_ctrl_fatal(ctrl, "failed to obtain write "
340 			    "lock");
341 		}
342 
343 		if (!libnvme_test_setup_ns(ctrl, test->wt_disc, LOCK_NSID,
344 		    lock_lbaf)) {
345 			libnvme_test_ctrl_fatal(ctrl, "failed to change state "
346 			    "to 0x%x", test->wt_disc);
347 		}
348 
349 		nvme_ctrl_unlock(ctrl);
350 
351 		if (!lock_info[i].li_lock_f(ctrl, ns)) {
352 			ret = false;
353 			continue;
354 		}
355 
356 		if (!test->wt_func(ctrl, lock_info[i].li_desc,
357 		    test->wt_errs[i])) {
358 			ret = false;
359 		}
360 
361 		lock_info[i].li_unlock_f(ctrl, ns);
362 	}
363 
364 	return (ret);
365 }
366 
367 int
main(void)368 main(void)
369 {
370 	int ret = EXIT_SUCCESS;
371 	nvme_t *nvme;
372 	nvme_ctrl_t *ctrl;
373 	nvme_ctrl_info_t *info;
374 	nvme_ns_t *ns;
375 
376 	libnvme_test_init(&nvme, &ctrl);
377 
378 	if (!nvme_ctrl_info_snap(ctrl, &info)) {
379 		libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot");
380 	}
381 
382 	if (!nvme_ns_init(ctrl, LOCK_NSID, &ns)) {
383 		libnvme_test_ctrl_fatal(ctrl, "failed to create ns %u "
384 		    "nvme_ns_t", LOCK_NSID);
385 	}
386 
387 	if (!libnvme_test_lbaf(info, NVME_TEST_LBA_SIZE, &lock_lbaf)) {
388 		errx(EXIT_FAILURE, "failed to find 4K LBA format, cannot "
389 		    "continue");
390 	}
391 
392 	for (size_t i = 0; i < ARRAY_SIZE(write_tests); i++) {
393 		if (!write_test_one(&write_tests[i], ctrl, ns))
394 			ret = EXIT_FAILURE;
395 	}
396 
397 	nvme_ns_fini(ns);
398 	nvme_ctrl_info_free(info);
399 	nvme_ctrl_fini(ctrl);
400 	nvme_fini(nvme);
401 
402 	if (ret == EXIT_SUCCESS) {
403 		(void) printf("All tests passed successfully\n");
404 	}
405 
406 	return (ret);
407 }
408