--- /dev/null
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "mock.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "../../src/nvme/ioctl.h"
+#include "util.h"
+
+struct mock_cmds {
+ const char *name;
+ const struct mock_cmd *cmds;
+ size_t remaining_cmds;
+};
+
+static int mock_fd = -1;
+static struct mock_cmds mock_admin_cmds = {.name = "admin"};
+static struct mock_cmds mock_io_cmds = {.name = "IO"};
+
+static void set_mock_cmds(
+ struct mock_cmds *mock_cmds, const struct mock_cmd *cmds, size_t len)
+{
+ mock_cmds->cmds = cmds;
+ mock_cmds->remaining_cmds = len;
+}
+
+static void mock_cmds_done(const struct mock_cmds *mock_cmds)
+{
+ check(!mock_cmds->remaining_cmds,
+ "%zu %s commands not executed",
+ mock_cmds->remaining_cmds, mock_cmds->name);
+}
+
+void set_mock_fd(int fd)
+{
+ mock_fd = fd;
+}
+
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_admin_cmds, cmds, len);
+}
+
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len)
+{
+ set_mock_cmds(&mock_io_cmds, cmds, len);
+}
+
+void end_mock_cmds(void)
+{
+ mock_cmds_done(&mock_admin_cmds);
+ mock_cmds_done(&mock_io_cmds);
+}
+
+#define execute_ioctl(cmd, mock_cmd) ({ \
+ check((cmd)->opcode == (mock_cmd)->opcode, \
+ "got opcode %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->opcode, (mock_cmd)->opcode); \
+ check((cmd)->flags == (mock_cmd)->flags, \
+ "got flags %" PRIu8 ", expected %" PRIu8, \
+ (cmd)->flags, (mock_cmd)->flags); \
+ check((cmd)->nsid == (mock_cmd)->nsid, \
+ "got nsid %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->nsid, (mock_cmd)->nsid); \
+ check((cmd)->cdw2 == (mock_cmd)->cdw2, \
+ "got cdw2 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw2, (mock_cmd)->cdw2); \
+ check((cmd)->cdw3 == (mock_cmd)->cdw3, \
+ "got cdw3 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw3, (mock_cmd)->cdw3); \
+ check((cmd)->metadata_len == (mock_cmd)->metadata_len, \
+ "got metadata_len %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->metadata_len, (mock_cmd)->metadata_len); \
+ cmp((void const *)(uintptr_t)(cmd)->metadata, \
+ (mock_cmd)->metadata, \
+ (cmd)->metadata_len, \
+ "incorrect metadata"); \
+ __u32 data_len = (cmd)->data_len; \
+ check(data_len == (mock_cmd)->data_len, \
+ "got data_len %" PRIu32 ", expected %" PRIu32, \
+ data_len, (mock_cmd)->data_len); \
+ void *data = (void *)(uintptr_t)(cmd)->addr; \
+ if ((mock_cmd)->in_data) { \
+ cmp(data, (mock_cmd)->in_data, data_len, "incorrect data"); \
+ } \
+ check((cmd)->cdw10 == (mock_cmd)->cdw10, \
+ "got cdw10 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw10, (mock_cmd)->cdw10); \
+ check((cmd)->cdw11 == (mock_cmd)->cdw11, \
+ "got cdw11 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw11, (mock_cmd)->cdw11); \
+ check((cmd)->cdw12 == (mock_cmd)->cdw12, \
+ "got cdw12 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw12, (mock_cmd)->cdw12); \
+ check((cmd)->cdw13 == (mock_cmd)->cdw13, \
+ "got cdw13 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw13, (mock_cmd)->cdw13); \
+ check((cmd)->cdw14 == (mock_cmd)->cdw14, \
+ "got cdw14 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw14, (mock_cmd)->cdw14); \
+ check((cmd)->cdw15 == (mock_cmd)->cdw15, \
+ "got cdw15 %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->cdw15, (mock_cmd)->cdw15); \
+ check((cmd)->timeout_ms == (mock_cmd)->timeout_ms, \
+ "got timeout_ms %" PRIu32 ", expected %" PRIu32, \
+ (cmd)->timeout_ms, (mock_cmd)->timeout_ms); \
+ (cmd)->result = (mock_cmd)->result; \
+ if ((mock_cmd)->out_data) { \
+ memcpy(data, (mock_cmd)->out_data, data_len); \
+ } \
+})
+
+int ioctl(int fd, unsigned long request, ...)
+{
+ struct mock_cmds *mock_cmds;
+ bool result64;
+ const struct mock_cmd *mock_cmd;
+ va_list args;
+ void *cmd;
+
+ check(fd == mock_fd, "got fd %d, expected %d", fd, mock_fd);
+ switch (request) {
+ case NVME_IOCTL_ADMIN_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_ADMIN64_CMD:
+ mock_cmds = &mock_admin_cmds;
+ result64 = true;
+ break;
+ case NVME_IOCTL_IO_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = false;
+ break;
+ case NVME_IOCTL_IO64_CMD:
+ mock_cmds = &mock_io_cmds;
+ result64 = true;
+ break;
+ default:
+ fail("unexpected %s %lu", __func__, request);
+ }
+ check(mock_cmds->remaining_cmds,
+ "unexpected %s command", mock_cmds->name);
+ mock_cmd = mock_cmds->cmds++;
+ mock_cmds->remaining_cmds--;
+
+ va_start(args, request);
+ cmd = va_arg(args, void *);
+ va_end(args);
+ if (result64) {
+ execute_ioctl((struct nvme_passthru_cmd64 *)cmd, mock_cmd);
+ } else {
+ check((uint32_t)mock_cmd->result == mock_cmd->result,
+ "expected 64-bit %s for result %" PRIu64,
+ __func__, mock_cmd->result);
+ execute_ioctl((struct nvme_passthru_cmd *)cmd, mock_cmd);
+ }
+ if (mock_cmd->err < 0) {
+ errno = -mock_cmd->err;
+ return -1;
+ }
+
+ return mock_cmd->err;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef _LIBNVME_TEST_IOCTL_MOCK_H
+#define _LIBNVME_TEST_IOCTL_MOCK_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * struct mock_cmd - a mock NVMe passthru ioctl() invocation
+ * @opcode: the expected `opcode` passed to ioctl()
+ * @flags: the expected `flags` passed to ioctl()
+ * @nsid: the expected `nsid` passed to ioctl()
+ * @cdw2: the expected `cdw2` passed to ioctl()
+ * @cdw3: the expected `cdw3` passed to ioctl()
+ * @metadata: the expected `metadata` of length `metadata_len` passed to ioctl()
+ * @in_data: the expected `addr` of length `data_len` passed to ioctl().
+ * Set this to NULL to skip checking the data,
+ * for example if the command is in the read direction.
+ * @metadata_len: the expected `metadata_len` passed to ioctl()
+ * @data_len: the expected `data_len` passed to ioctl()
+ * @cdw10: the expected `cdw10` passed to ioctl()
+ * @cdw11: the expected `cdw11` passed to ioctl()
+ * @cdw12: the expected `cdw12` passed to ioctl()
+ * @cdw13: the expected `cdw13` passed to ioctl()
+ * @cdw14: the expected `cdw14` passed to ioctl()
+ * @cdw15: the expected `cdw15` passed to ioctl()
+ * @timeout_ms: the expected `timeout_ms` passed to ioctl()
+ * @out_data: if not NULL, `data_len` bytes to copy to the caller's `addr`
+ * @result: copied to the caller's `result`.
+ * If `result` doesn't fit in a u32, the ioctl() must be the 64-bit one.
+ * @err: If negative, ioctl() returns -1 and sets `errno` to `-err`.
+ * Otherwise, ioctl() returns `err`, representing a NVMe status code.
+ */
+struct mock_cmd {
+ uint8_t opcode;
+ uint8_t flags;
+ uint32_t nsid;
+ uint32_t cdw2;
+ uint32_t cdw3;
+ const void *metadata;
+ const void *in_data;
+ uint32_t metadata_len;
+ uint32_t data_len;
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+ uint32_t timeout_ms;
+ const void *out_data;
+ uint64_t result;
+ int err;
+};
+
+/**
+ * set_mock_fd() - sets the expected file descriptor for NVMe passthru ioctls()
+ * @fd: file descriptor expected to be passed to ioctl()
+ */
+void set_mock_fd(int fd);
+
+/**
+ * set_mock_admin_cmds() - mocks NVMe admin passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe admin passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_io_cmds(), but for admin commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_admin_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * set_mock_io_cmds() - mocks NVMe IO passthru ioctl() invocations
+ * @cmds: pointer to start of the mock_cmd slice
+ * @len: length of the mock_cmd slice (number of ioctl() invocations)
+ *
+ * Provides a sequence of mocks for NVMe IO passthru ioctl() invocations.
+ * Each ioctl() consumes the next mock from the sequence.
+ * Its arguments are checked against the mock's expected arguments,
+ * aborting the process if unexpected arguments are passed.
+ * The mock results (return value, NVMe result and data)
+ * are returned from the ioctl().
+ *
+ * Analogous to set_mock_admin_cmds(), but for IO commands.
+ * Both admin and IO mocks can be active at the same time.
+ */
+void set_mock_io_cmds(const struct mock_cmd *cmds, size_t len);
+
+/**
+ * end_mock_cmds() - finishes mocking NVMe passthru ioctl() invocations
+ *
+ * Checks that all mock ioctl() invocations were performed.
+ */
+void end_mock_cmds(void);
+
+#endif /* #ifndef _LIBNVME_TEST_IOCTL_MOCK_H */