/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * Common functions for the various ioctl tests that we're using. */ #include #include #include #include #include #include #include "nvme_ioctl_util.h" /* * Cached copies of devinfo nodes to speed up subsequent lookups. */ static di_node_t nvme_test_root; static di_node_t nvme_test_dev; /* * Lock ioctl template structures. These are all non-blocking locks because we * don't want the tests to hang in error. */ const nvme_ioctl_lock_t nvme_test_ctrl_wrlock = { .nil_ent = NVME_LOCK_E_CTRL, .nil_level = NVME_LOCK_L_WRITE, .nil_flags = NVME_LOCK_F_DONT_BLOCK }; const nvme_ioctl_lock_t nvme_test_ctrl_rdlock = { .nil_ent = NVME_LOCK_E_CTRL, .nil_level = NVME_LOCK_L_READ, .nil_flags = NVME_LOCK_F_DONT_BLOCK }; const nvme_ioctl_lock_t nvme_test_ns_wrlock = { .nil_common = { .nioc_nsid = 1 }, .nil_ent = NVME_LOCK_E_NS, .nil_level = NVME_LOCK_L_WRITE, .nil_flags = NVME_LOCK_F_DONT_BLOCK }; const nvme_ioctl_lock_t nvme_test_ns_rdlock = { .nil_common = { .nioc_nsid = 1 }, .nil_ent = NVME_LOCK_E_NS, .nil_level = NVME_LOCK_L_READ, .nil_flags = NVME_LOCK_F_DONT_BLOCK }; const nvme_ioctl_unlock_t nvme_test_ctrl_unlock = { .niu_ent = NVME_LOCK_E_CTRL }; const nvme_ioctl_unlock_t nvme_test_ns_unlock = { .niu_common = { .nioc_nsid = 1 }, .niu_ent = NVME_LOCK_E_NS }; static int nvme_ioctl_test_find_nsid(di_node_t di, uint32_t nsid) { int fd; const char *type; char name[128], *mpath, path[PATH_MAX]; di_minor_t minor; if (nsid == 0) { type = DDI_NT_NVME_NEXUS; (void) strlcpy(name, "devctl", sizeof (name)); } else { type = DDI_NT_NVME_ATTACHMENT_POINT; (void) snprintf(name, sizeof (name), "%u", nsid); } minor = DI_MINOR_NIL; while ((minor = di_minor_next(di, minor)) != DI_MINOR_NIL) { if (strcmp(di_minor_nodetype(minor), type) == 0 && strcmp(di_minor_name(minor), name) == 0) { break; } } if (minor == DI_MINOR_NIL) { errx(EXIT_FAILURE, "failed to find minor for nsid %u on %s%d", nsid, di_driver_name(di), di_instance(di)); } mpath = di_devfs_minor_path(minor); if (mpath == NULL) { err(EXIT_FAILURE, "failed to get minor device path for nsid %u " "on %s%d", nsid, di_driver_name(di), di_instance(di)); } if (snprintf(path, sizeof (path), "/devices%s", mpath) >= sizeof (path)) { errx(EXIT_FAILURE, "failed to construct full /devices path for " "%s: snprintf buffer would have overflowed", mpath); } di_devfs_path_free(mpath); fd = open(path, O_RDWR); if (fd < 0) { err(EXIT_FAILURE, "failed to open minor path %s", path); } return (fd); } /* * The ioctl tests expect an NVMe device to be nominated for use to test * against. Translate that device into an fd. */ int nvme_ioctl_test_get_fd(uint32_t nsid) { const char *dev, *errstr; long long ll; if (nvme_test_dev != NULL) { return (nvme_ioctl_test_find_nsid(nvme_test_dev, nsid)); } dev = getenv(NVME_TEST_DEV_ENVVAR); if (dev == NULL) { errx(EXIT_FAILURE, "cannot run test, missing required NVMe " "device, please set the %s environment variable", NVME_TEST_DEV_ENVVAR); } if (strncmp("nvme", dev, 4) != 0) { errx(EXIT_FAILURE, "%s environment variable device %s does " "not begin with 'nvme'", NVME_TEST_DEV_ENVVAR, dev); } ll = strtonum(dev + 4, 0, INT32_MAX, &errstr); if (errstr != NULL) { errx(EXIT_FAILURE, "failed to parse %s environment variable " "device %s instance: value is %s", NVME_TEST_DEV_ENVVAR, dev, errstr); } if (nvme_test_root == NULL) { nvme_test_root = di_init("/", DINFOCPYALL); if (nvme_test_root == DI_NODE_NIL) { err(EXIT_FAILURE, "failed to initialize libdevinfo"); } } for (di_node_t di = di_drv_first_node("nvme", nvme_test_root); di != DI_NODE_NIL; di = di_drv_next_node(di)) { if (di_instance(di) == (int)ll) { nvme_test_dev = di; return (nvme_ioctl_test_find_nsid(di, nsid)); } } errx(EXIT_FAILURE, "failed to find %s environment variable device %s: " "cannot run test", NVME_TEST_DEV_ENVVAR, dev); } /* * This is a wrapper that requires we successfully lock something. */ void nvme_ioctl_test_lock(int fd, const nvme_ioctl_lock_t *lockp) { nvme_ioctl_lock_t lock = *lockp; const char *targ = lockp->nil_ent == NVME_LOCK_E_CTRL ? "controller" : "namespace"; const char *level = lockp->nil_level == NVME_LOCK_L_READ ? "read" : "write"; if (ioctl(fd, NVME_IOC_LOCK, &lock) != 0) { err(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due " "to failure to issue %s %s lock ioctl", targ, level); } else if (lock.nil_common.nioc_drv_err != NVME_IOCTL_E_OK) { errx(EXIT_FAILURE, "TEST FAILED: cannot proceed with tests due " "to failure to obtain %s %s lock, got 0x%x", targ, level, lock.nil_common.nioc_drv_err); } } /* * Determine if a thread is blocked in our locking ioctl. We use proc_sysname() * so we can avoid encoding the system call number of the ioctl into the test * directly. */ bool nvme_ioctl_test_thr_blocked(thread_t thr) { lwpstatus_t lwp; char name[SYS2STR_MAX]; if (proc_get_lwpstatus(getpid(), (uint_t)thr, &lwp) != 0) { err(EXIT_FAILURE, "TEST FAILED: unable to continue test " "execution as we failed to retrieve the lwpsinfo_t data " "for thread 0x%x", thr); } if ((lwp.pr_flags & PR_ASLEEP) == 0) return (false); if (proc_sysname(lwp.pr_syscall, name, sizeof (name)) == NULL) return (false); return (strcmp(name, "ioctl") == 0); }