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 2024 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 75 nvme_ioctl_test_find_nsid(di_node_t di, uint32_t nsid) 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, O_RDWR); 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 129 nvme_ioctl_test_get_fd(uint32_t nsid) 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)); 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)); 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 /* 177 * This is a wrapper that requires we successfully lock something. 178 */ 179 void 180 nvme_ioctl_test_lock(int fd, const nvme_ioctl_lock_t *lockp) 181 { 182 nvme_ioctl_lock_t lock = *lockp; 183 const char *targ = lockp->nil_ent == NVME_LOCK_E_CTRL ? 184 "controller" : "namespace"; 185 const char *level = lockp->nil_level == NVME_LOCK_L_READ ? 186 "read" : "write"; 187 188 if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) { 189 err(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due " 190 "to failure to issue %s %s lock ioctl", targ, level); 191 } else if (lock.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) { 192 errx(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due " 193 "to failure to obtain %s %s lock, got 0x%x", targ, level, 194 lock.nil_common.nioc_drv_err); 195 } 196 } 197 198 /* 199 * Determine if a thread is blocked in our locking ioctl. We use proc_sysname() 200 * so we can avoid encoding the system call number of the ioctl into the test 201 * directly. 202 */ 203 bool 204 nvme_ioctl_test_thr_blocked(thread_t thr) 205 { 206 lwpstatus_t lwp; 207 char name[SYS2STR_MAX]; 208 209 if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) { 210 err(EXIT_FAILURE, "TEST FAILED: unable to continue test " 211 "execution as we failed to retrieve the lwpsinfo_t data " 212 "for thread 0x%x", thr); 213 } 214 215 if ((lwp.pr_flags & PR_ASLEEP) == 0) 216 return (false); 217 218 if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == NULL) 219 return (false); 220 221 return (strcmp(name, "ioctl") == 0); 222 } 223