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 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 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 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 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 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 * 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