diff options
Diffstat (limited to 'src/nvme/ioctl.c')
-rw-r--r-- | src/nvme/ioctl.c | 2258 |
1 files changed, 2258 insertions, 0 deletions
diff --git a/src/nvme/ioctl.c b/src/nvme/ioctl.c new file mode 100644 index 0000000..9090b7e --- /dev/null +++ b/src/nvme/ioctl.c @@ -0,0 +1,2258 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2020 Western Digital Corporation or its affiliates. + * + * Authors: Keith Busch <keith.busch@wdc.com> + * Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com> + */ +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <ccan/build_assert/build_assert.h> +#include <ccan/endian/endian.h> + +#include "ioctl.h" +#include "util.h" + +static bool nvme_debug; + +static int nvme_verify_chr(int fd) +{ + static struct stat nvme_stat; + int err = fstat(fd, &nvme_stat); + + if (err < 0) + return errno; + + if (!S_ISCHR(nvme_stat.st_mode)) { + errno = ENOTBLK; + return -1; + } + return 0; +} + +int nvme_subsystem_reset(int fd) +{ + int ret; + + ret = nvme_verify_chr(fd); + if (ret) + return ret; + return ioctl(fd, NVME_IOCTL_SUBSYS_RESET); +} + +int nvme_ctrl_reset(int fd) +{ + int ret; + + ret = nvme_verify_chr(fd); + if (ret) + return ret; + return ioctl(fd, NVME_IOCTL_RESET); +} + +int nvme_ns_rescan(int fd) +{ + int ret; + + ret = nvme_verify_chr(fd); + if (ret) + return ret; + return ioctl(fd, NVME_IOCTL_RESCAN); +} + +int nvme_get_nsid(int fd, __u32 *nsid) +{ + errno = 0; + *nsid = ioctl(fd, NVME_IOCTL_ID); + return -1 * (errno != 0); +} + +static int nvme_submit_passthru64(int fd, unsigned long ioctl_cmd, + struct nvme_passthru_cmd64 *cmd, + __u64 *result) +{ + int err = ioctl(fd, ioctl_cmd, cmd); + + if (err >= 0 && result) + *result = cmd->result; + return err; +} + +static void nvme_show_command(struct nvme_passthru_cmd *cmd, int err, struct timeval start, + struct timeval end) +{ + printf("opcode : %02x\n", cmd->opcode); + printf("flags : %02x\n", cmd->flags); + printf("rsvd1 : %04x\n", cmd->rsvd1); + printf("nsid : %08x\n", cmd->nsid); + printf("cdw2 : %08x\n", cmd->cdw2); + printf("cdw3 : %08x\n", cmd->cdw3); + printf("data_len : %08x\n", cmd->data_len); + printf("metadata_len : %08x\n", cmd->metadata_len); + printf("addr : %"PRIx64"\n", (uint64_t)(uintptr_t)cmd->addr); + printf("metadata : %"PRIx64"\n", (uint64_t)(uintptr_t)cmd->metadata); + printf("cdw10 : %08x\n", cmd->cdw10); + printf("cdw11 : %08x\n", cmd->cdw11); + printf("cdw12 : %08x\n", cmd->cdw12); + printf("cdw13 : %08x\n", cmd->cdw13); + printf("cdw14 : %08x\n", cmd->cdw14); + printf("cdw15 : %08x\n", cmd->cdw15); + printf("timeout_ms : %08x\n", cmd->timeout_ms); + printf("result : %08x\n", cmd->result); + printf("err : %d\n", err); + printf("latency : %lu us\n", + (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec)); +} + +void nvme_set_debug(bool debug) +{ + nvme_debug = debug; +} + +bool nvme_get_debug(void) +{ + return nvme_debug; +} + +static int nvme_submit_passthru(int fd, unsigned long ioctl_cmd, + struct nvme_passthru_cmd *cmd, __u32 *result) +{ + struct timeval start; + struct timeval end; + int err; + + if (nvme_get_debug()) + gettimeofday(&start, NULL); + + err = ioctl(fd, ioctl_cmd, cmd); + + if (nvme_get_debug()) { + gettimeofday(&end, NULL); + nvme_show_command(cmd, err, start, end); + } + + if (err >= 0 && result) + *result = cmd->result; + + return err; +} + +static int nvme_passthru64(int fd, unsigned long ioctl_cmd, __u8 opcode, + __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, + __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, + __u32 cdw13, __u32 cdw14, __u32 cdw15, + __u32 data_len, void *data, __u32 metadata_len, + void *metadata, __u32 timeout_ms, __u64 *result) +{ + struct nvme_passthru_cmd64 cmd = { + .opcode = opcode, + .flags = flags, + .rsvd1 = rsvd, + .nsid = nsid, + .cdw2 = cdw2, + .cdw3 = cdw3, + .metadata = (__u64)(uintptr_t)metadata, + .addr = (__u64)(uintptr_t)data, + .metadata_len = metadata_len, + .data_len = data_len, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .cdw14 = cdw14, + .cdw15 = cdw15, + .timeout_ms = timeout_ms, + }; + + return nvme_submit_passthru64(fd, ioctl_cmd, &cmd, result); +} + +static int nvme_passthru(int fd, unsigned long ioctl_cmd, __u8 opcode, + __u8 flags, __u16 rsvd, __u32 nsid, __u32 cdw2, + __u32 cdw3, __u32 cdw10, __u32 cdw11, __u32 cdw12, + __u32 cdw13, __u32 cdw14, __u32 cdw15, __u32 data_len, + void *data, __u32 metadata_len, void *metadata, + __u32 timeout_ms, __u32 *result) +{ + struct nvme_passthru_cmd cmd = { + .opcode = opcode, + .flags = flags, + .rsvd1 = rsvd, + .nsid = nsid, + .cdw2 = cdw2, + .cdw3 = cdw3, + .metadata = (__u64)(uintptr_t)metadata, + .addr = (__u64)(uintptr_t)data, + .metadata_len = metadata_len, + .data_len = data_len, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .cdw14 = cdw14, + .cdw15 = cdw15, + .timeout_ms = timeout_ms, + }; + + return nvme_submit_passthru(fd, ioctl_cmd, &cmd, result); +} + +int nvme_submit_admin_passthru64(int fd, struct nvme_passthru_cmd64 *cmd, + __u64 *result) +{ + return nvme_submit_passthru64(fd, NVME_IOCTL_ADMIN64_CMD, cmd, result); +} + +int nvme_admin_passthru64(int fd, __u8 opcode, __u8 flags, __u16 rsvd, + __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, + __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, + __u32 cdw15, __u32 data_len, void *data, + __u32 metadata_len, void *metadata, __u32 timeout_ms, + __u64 *result) +{ + return nvme_passthru64(fd, NVME_IOCTL_ADMIN64_CMD, opcode, flags, rsvd, + nsid, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, + cdw14, cdw15, data_len, data, metadata_len, + metadata, timeout_ms, result); +} + +int nvme_submit_admin_passthru(int fd, struct nvme_passthru_cmd *cmd, __u32 *result) +{ + return nvme_submit_passthru(fd, NVME_IOCTL_ADMIN_CMD, cmd, result); +} + +int nvme_admin_passthru(int fd, __u8 opcode, __u8 flags, __u16 rsvd, + __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, + __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, + __u32 cdw15, __u32 data_len, void *data, + __u32 metadata_len, void *metadata, __u32 timeout_ms, + __u32 *result) +{ + return nvme_passthru(fd, NVME_IOCTL_ADMIN_CMD, opcode, flags, rsvd, + nsid, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, + cdw14, cdw15, data_len, data, metadata_len, + metadata, timeout_ms, result); +} + +enum nvme_cmd_dword_fields { + NVME_DEVICE_SELF_TEST_CDW10_STC_SHIFT = 0, + NVME_DEVICE_SELF_TEST_CDW10_STC_MASK = 0xf, + NVME_DIRECTIVE_CDW11_DOPER_SHIFT = 0, + NVME_DIRECTIVE_CDW11_DTYPE_SHIFT = 8, + NVME_DIRECTIVE_CDW11_DPSEC_SHIFT = 16, + NVME_DIRECTIVE_CDW11_DOPER_MASK = 0xff, + NVME_DIRECTIVE_CDW11_DTYPE_MASK = 0xff, + NVME_DIRECTIVE_CDW11_DPSEC_MASK = 0xffff, + NVME_DIRECTIVE_SEND_IDENTIFY_CDW12_ENDIR_SHIFT = 0, + NVME_DIRECTIVE_SEND_IDENTIFY_CDW12_DTYPE_SHIFT = 1, + NVME_DIRECTIVE_SEND_IDENTIFY_CDW12_ENDIR_MASK = 0x1, + NVME_DIRECTIVE_SEND_IDENTIFY_CDW12_DTYPE_MASK = 0x1, + NVME_FW_COMMIT_CDW10_FS_SHIFT = 0, + NVME_FW_COMMIT_CDW10_CA_SHIFT = 3, + NVME_FW_COMMIT_CDW10_BPID_SHIFT = 31, + NVME_FW_COMMIT_CDW10_FS_MASK = 0x7, + NVME_FW_COMMIT_CDW10_CA_MASK = 0x7, + NVME_FW_COMMIT_CDW10_BPID_MASK = 0x1, + NVME_GET_FEATURES_CDW10_SEL_SHIFT = 8, + NVME_GET_FEATURES_CDW10_SEL_MASK = 0x7, + NVME_SET_FEATURES_CDW10_SAVE_SHIFT = 31, + NVME_SET_FEATURES_CDW10_SAVE_MASK = 0x1, + NVME_FEATURES_CDW10_FID_SHIFT = 0, + NVME_FEATURES_CDW14_UUID_SHIFT = 0, + NVME_FEATURES_CDW10_FID_MASK = 0xff, + NVME_FEATURES_CDW14_UUID_MASK = 0x7f, + NVME_LOG_CDW10_LID_SHIFT = 0, + NVME_LOG_CDW10_LSP_SHIFT = 8, + NVME_LOG_CDW10_RAE_SHIFT = 15, + NVME_LOG_CDW10_NUMDL_SHIFT = 16, + NVME_LOG_CDW11_NUMDU_SHIFT = 0, + NVME_LOG_CDW11_LSI_SHIFT = 16, + NVME_LOG_CDW14_UUID_SHIFT = 0, + NVME_LOG_CDW14_CSI_SHIFT = 24, + NVME_LOG_CDW14_OT_SHIFT = 23, + NVME_LOG_CDW10_LID_MASK = 0xff, + NVME_LOG_CDW10_LSP_MASK = 0x7f, + NVME_LOG_CDW10_RAE_MASK = 0x1, + NVME_LOG_CDW10_NUMDL_MASK = 0xffff, + NVME_LOG_CDW11_NUMDU_MASK = 0xffff, + NVME_LOG_CDW11_LSI_MASK = 0xffff, + NVME_LOG_CDW14_UUID_MASK = 0x7f, + NVME_LOG_CDW14_CSI_MASK = 0xff, + NVME_LOG_CDW14_OT_MASK = 0x1, + NVME_IDENTIFY_CDW10_CNS_SHIFT = 0, + NVME_IDENTIFY_CDW10_CNTID_SHIFT = 16, + NVME_IDENTIFY_CDW11_CNSSPECID_SHIFT = 0, + NVME_IDENTIFY_CDW14_UUID_SHIFT = 0, + NVME_IDENTIFY_CDW11_CSI_SHIFT = 24, + NVME_IDENTIFY_CDW10_CNS_MASK = 0xff, + NVME_IDENTIFY_CDW10_CNTID_MASK = 0xffff, + NVME_IDENTIFY_CDW11_CNSSPECID_MASK = 0xffff, + NVME_IDENTIFY_CDW14_UUID_MASK = 0x7f, + NVME_IDENTIFY_CDW11_CSI_MASK = 0xff, + NVME_NAMESPACE_ATTACH_CDW10_SEL_SHIFT = 0, + NVME_NAMESPACE_ATTACH_CDW10_SEL_MASK = 0xf, + NVME_NAMESPACE_MGMT_CDW10_SEL_SHIFT = 0, + NVME_NAMESPACE_MGMT_CDW10_SEL_MASK = 0xf, + NVME_NAMESPACE_MGMT_CDW11_CSI_SHIFT = 24, + NVME_NAMESPACE_MGMT_CDW11_CSI_MASK = 0xff, + NVME_VIRT_MGMT_CDW10_ACT_SHIFT = 0, + NVME_VIRT_MGMT_CDW10_RT_SHIFT = 8, + NVME_VIRT_MGMT_CDW10_CNTLID_SHIFT = 16, + NVME_VIRT_MGMT_CDW11_NR_SHIFT = 0, + NVME_VIRT_MGMT_CDW10_ACT_MASK = 0xf, + NVME_VIRT_MGMT_CDW10_RT_MASK = 0x7, + NVME_VIRT_MGMT_CDW10_CNTLID_MASK = 0xffff, + NVME_VIRT_MGMT_CDW11_NR_MASK = 0xffff, + NVME_FORMAT_CDW10_LBAF_SHIFT = 0, + NVME_FORMAT_CDW10_MSET_SHIFT = 4, + NVME_FORMAT_CDW10_PI_SHIFT = 5, + NVME_FORMAT_CDW10_PIL_SHIFT = 8, + NVME_FORMAT_CDW10_SES_SHIFT = 9, + NVME_FORMAT_CDW10_LBAFU_SHIFT = 12, + NVME_FORMAT_CDW10_LBAF_MASK = 0xf, + NVME_FORMAT_CDW10_MSET_MASK = 0x1, + NVME_FORMAT_CDW10_PI_MASK = 0x7, + NVME_FORMAT_CDW10_PIL_MASK = 0x1, + NVME_FORMAT_CDW10_SES_MASK = 0x7, + NVME_FORMAT_CDW10_LBAFU_MASK = 0x3, + NVME_SANITIZE_CDW10_SANACT_SHIFT = 0, + NVME_SANITIZE_CDW10_AUSE_SHIFT = 3, + NVME_SANITIZE_CDW10_OWPASS_SHIFT = 4, + NVME_SANITIZE_CDW10_OIPBP_SHIFT = 8, + NVME_SANITIZE_CDW10_NODAS_SHIFT = 9, + NVME_SANITIZE_CDW10_SANACT_MASK = 0x7, + NVME_SANITIZE_CDW10_AUSE_MASK = 0x1, + NVME_SANITIZE_CDW10_OWPASS_MASK = 0xf, + NVME_SANITIZE_CDW10_OIPBP_MASK = 0x1, + NVME_SANITIZE_CDW10_NODAS_MASK = 0x1, + NVME_SECURITY_NSSF_SHIFT = 0, + NVME_SECURITY_SPSP0_SHIFT = 8, + NVME_SECURITY_SPSP1_SHIFT = 16, + NVME_SECURITY_SECP_SHIFT = 24, + NVME_SECURITY_NSSF_MASK = 0xff, + NVME_SECURITY_SPSP0_MASK = 0xff, + NVME_SECURITY_SPSP1_MASK = 0xff, + NVME_SECURITY_SECP_MASK = 0xffff, + NVME_GET_LBA_STATUS_CDW13_RL_SHIFT = 0, + NVME_GET_LBA_STATUS_CDW13_ATYPE_SHIFT = 24, + NVME_GET_LBA_STATUS_CDW13_RL_MASK = 0xffff, + NVME_GET_LBA_STATUS_CDW13_ATYPE_MASK = 0xff, + NVME_ZNS_MGMT_SEND_ZSASO_SHIFT = 9, + NVME_ZNS_MGMT_SEND_ZSASO_MASK = 0x1, + NVME_ZNS_MGMT_SEND_SEL_SHIFT = 8, + NVME_ZNS_MGMT_SEND_SEL_MASK = 0x1, + NVME_ZNS_MGMT_SEND_ZSA_SHIFT = 0, + NVME_ZNS_MGMT_SEND_ZSA_MASK = 0xff, + NVME_ZNS_MGMT_RECV_ZRA_SHIFT = 0, + NVME_ZNS_MGMT_RECV_ZRA_MASK = 0xff, + NVME_ZNS_MGMT_RECV_ZRASF_SHIFT = 8, + NVME_ZNS_MGMT_RECV_ZRASF_MASK = 0xff, + NVME_ZNS_MGMT_RECV_ZRAS_FEAT_SHIFT = 16, + NVME_ZNS_MGMT_RECV_ZRAS_FEAT_MASK = 0x1, + NVME_DIM_TAS_SHIFT = 0, + NVME_DIM_TAS_MASK = 0xF, +}; + +enum features { + NVME_FEATURES_ARBITRATION_BURST_SHIFT = 0, + NVME_FEATURES_ARBITRATION_LPW_SHIFT = 8, + NVME_FEATURES_ARBITRATION_MPW_SHIFT = 16, + NVME_FEATURES_ARBITRATION_HPW_SHIFT = 24, + NVME_FEATURES_ARBITRATION_BURST_MASK = 0x7, + NVME_FEATURES_ARBITRATION_LPW_MASK = 0xff, + NVME_FEATURES_ARBITRATION_MPW_MASK = 0xff, + NVME_FEATURES_ARBITRATION_HPW_MASK = 0xff, + NVME_FEATURES_PWRMGMT_PS_SHIFT = 0, + NVME_FEATURES_PWRMGMT_WH_SHIFT = 5, + NVME_FEATURES_PWRMGMT_PS_MASK = 0x1f, + NVME_FEATURES_PWRMGMT_WH_MASK = 0x7, + NVME_FEATURES_TMPTH_SHIFT = 0, + NVME_FEATURES_TMPSEL_SHIFT = 16, + NVME_FEATURES_THSEL_SHIFT = 20, + NVME_FEATURES_TMPTH_MASK = 0xff, + NVME_FEATURES_TMPSEL_MASK = 0xf, + NVME_FEATURES_THSEL_MASK = 0x3, + NVME_FEATURES_ERROR_RECOVERY_TLER_SHIFT = 0, + NVME_FEATURES_ERROR_RECOVERY_DULBE_SHIFT = 16, + NVME_FEATURES_ERROR_RECOVERY_TLER_MASK = 0xff, + NVME_FEATURES_ERROR_RECOVERY_DULBE_MASK = 0x1, + NVME_FEATURES_VWC_WCE_SHIFT = 0, + NVME_FEATURES_VWC_WCE_MASK = 0x1, + NVME_FEATURES_IRQC_THR_SHIFT = 0, + NVME_FEATURES_IRQC_TIME_SHIFT = 8, + NVME_FEATURES_IRQC_THR_MASK = 0xff, + NVME_FEATURES_IRQC_TIME_MASK = 0xff, + NVME_FEATURES_IVC_IV_SHIFT = 0, + NVME_FEATURES_IVC_CD_SHIFT = 16, + NVME_FEATURES_IVC_IV_MASK = 0xffff, + NVME_FEATURES_IVC_CD_MASK = 0x1, + NVME_FEATURES_WAN_DN_SHIFT = 0, + NVME_FEATURES_WAN_DN_MASK = 0x1, + NVME_FEATURES_APST_APSTE_SHIFT = 0, + NVME_FEATURES_APST_APSTE_MASK = 0x1, + NVME_FEATURES_HCTM_TMT2_SHIFT = 0, + NVME_FEATURES_HCTM_TMT1_SHIFT = 16, + NVME_FEATURES_HCTM_TMT2_MASK = 0xffff, + NVME_FEATURES_HCTM_TMT1_MASK = 0xffff, + NVME_FEATURES_NOPS_NOPPME_SHIFT = 0, + NVME_FEATURES_NOPS_NOPPME_MASK = 0x1, + NVME_FEATURES_PLM_PLE_SHIFT = 0, + NVME_FEATURES_PLM_PLE_MASK = 0x1, + NVME_FEATURES_PLM_WINDOW_SELECT_SHIFT = 0, + NVME_FEATURES_PLM_WINDOW_SELECT_MASK = 0xf, + NVME_FEATURES_LBAS_LSIRI_SHIFT = 0, + NVME_FEATURES_LBAS_LSIPI_SHIFT = 16, + NVME_FEATURES_LBAS_LSIRI_MASK = 0xffff, + NVME_FEATURES_LBAS_LSIPI_MASK = 0xffff, + NVME_FEATURES_IOCSP_IOCSCI_SHIFT = 0, + NVME_FEATURES_IOCSP_IOCSCI_MASK = 0xff, +}; + +int nvme_identify(struct nvme_identify_args *args) +{ + __u32 cdw10 = NVME_SET(args->cntid, IDENTIFY_CDW10_CNTID) | + NVME_SET(args->cns, IDENTIFY_CDW10_CNS); + __u32 cdw11 = NVME_SET(args->cns_specific_id, IDENTIFY_CDW11_CNSSPECID) | + NVME_SET(args->csi, IDENTIFY_CDW11_CSI); + __u32 cdw14 = NVME_SET(args->uuidx, IDENTIFY_CDW14_UUID); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_identify, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->data, + .data_len = NVME_IDENTIFY_DATA_SIZE, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw14 = cdw14, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_get_log(struct nvme_get_log_args *args) +{ + __u32 numd = (args->len >> 2) - 1; + __u16 numdu = numd >> 16, numdl = numd & 0xffff; + + __u32 cdw10 = NVME_SET(args->lid, LOG_CDW10_LID) | + NVME_SET(args->lsp, LOG_CDW10_LSP) | + NVME_SET(!!args->rae, LOG_CDW10_RAE) | + NVME_SET(numdl, LOG_CDW10_NUMDL); + __u32 cdw11 = NVME_SET(numdu, LOG_CDW11_NUMDU) | + NVME_SET(args->lsi, LOG_CDW11_LSI); + __u32 cdw12 = args->lpo & 0xffffffff; + __u32 cdw13 = args->lpo >> 32; + __u32 cdw14 = NVME_SET(args->uuidx, LOG_CDW14_UUID) | + NVME_SET(!!args->ot, LOG_CDW14_OT) | + NVME_SET(args->csi, LOG_CDW14_CSI); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_get_log_page, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->log, + .data_len = args->len, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .cdw14 = cdw14, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(struct nvme_get_log_args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_get_log_page(int fd, __u32 xfer_len, struct nvme_get_log_args *args) +{ + __u64 offset = 0, xfer, data_len = args->len; + __u64 start = args->lpo; + bool retain = args->rae; + void *ptr = args->log; + int ret; + + args->fd = fd; + + /* + * 4k is the smallest possible transfer unit, so restricting to 4k + * avoids having to check the MDTS value of the controller. + */ + do { + xfer = data_len - offset; + if (xfer > xfer_len) + xfer = xfer_len; + + /* + * Always retain regardless of the RAE parameter until the very + * last portion of this log page so the data remains latched + * during the fetch sequence. + */ + args->lpo = start + offset; + args->len = xfer; + args->log = ptr; + args->rae = offset + xfer < data_len || retain; + ret = nvme_get_log(args); + if (ret) + return ret; + + offset += xfer; + ptr += xfer; + } while (offset < data_len); + + return 0; +} + +int nvme_set_features(struct nvme_set_features_args *args) +{ + __u32 cdw10 = NVME_SET(args->fid, FEATURES_CDW10_FID) | + NVME_SET(!!args->save, SET_FEATURES_CDW10_SAVE); + __u32 cdw14 = NVME_SET(args->uuidx, FEATURES_CDW14_UUID); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_set_features, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .cdw10 = cdw10, + .cdw11 = args->cdw11, + .cdw12 = args->cdw12, + .cdw13 = args->cdw13, + .cdw14 = cdw14, + .cdw15 = args->cdw15, + .timeout_ms = args->timeout, + }; + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +static int __nvme_set_features(int fd, __u8 fid, __u32 cdw11, bool save, + __u32 *result) +{ + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = fid, + .nsid = NVME_NSID_NONE, + .cdw11 = cdw11, + .cdw12 = 0, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + return nvme_set_features(&args); +} + +int nvme_set_features_arbitration(int fd, __u8 ab, __u8 lpw, __u8 mpw, + __u8 hpw, bool save, __u32 *result) +{ + __u32 value = NVME_SET(ab, FEAT_ARBITRATION_BURST) | + NVME_SET(lpw, FEAT_ARBITRATION_LPW) | + NVME_SET(mpw, FEAT_ARBITRATION_MPW) | + NVME_SET(hpw, FEAT_ARBITRATION_HPW); + + return __nvme_set_features(fd, NVME_FEAT_FID_ARBITRATION, value, save, + result); +} + +int nvme_set_features_power_mgmt(int fd, __u8 ps, __u8 wh, bool save, + __u32 *result) +{ + __u32 value = NVME_SET(ps, FEAT_PWRMGMT_PS) | + NVME_SET(wh, FEAT_PWRMGMT_WH); + + return __nvme_set_features(fd, NVME_FEAT_FID_POWER_MGMT, value, save, + result); +} + +int nvme_set_features_lba_range(int fd, __u32 nsid, __u8 nr_ranges, bool save, + struct nvme_lba_range_type *data, __u32 *result) +{ + return nvme_set_features_data( + fd, NVME_FEAT_FID_LBA_RANGE, nsid, nr_ranges - 1, save, + sizeof(*data), data, result); +} + +int nvme_set_features_temp_thresh(int fd, __u16 tmpth, __u8 tmpsel, + enum nvme_feat_tmpthresh_thsel thsel, + bool save, __u32 *result) +{ + __u32 value = NVME_SET(tmpth, FEAT_TT_TMPTH) | + NVME_SET(tmpsel, FEAT_TT_TMPSEL) | + NVME_SET(thsel, FEAT_TT_THSEL); + + return __nvme_set_features(fd, NVME_FEAT_FID_TEMP_THRESH, value, save, + result); +} + +int nvme_set_features_err_recovery(int fd, __u32 nsid, __u16 tler, bool dulbe, + bool save, __u32 *result) +{ + __u32 value = NVME_SET(tler, FEAT_ERROR_RECOVERY_TLER) | + NVME_SET(!!dulbe, FEAT_ERROR_RECOVERY_DULBE); + + return nvme_set_features_simple( + fd, NVME_FEAT_FID_ERR_RECOVERY, nsid, value, save, result); +} + +int nvme_set_features_volatile_wc(int fd, bool wce, bool save, __u32 *result) +{ + __u32 value = NVME_SET(!!wce, FEAT_VWC_WCE); + + return __nvme_set_features(fd, NVME_FEAT_FID_VOLATILE_WC, value, save, + result); +} + +int nvme_set_features_irq_coalesce(int fd, __u8 thr, __u8 time, bool save, + __u32 *result) +{ + __u32 value = NVME_SET(thr, FEAT_IRQC_THR) | + NVME_SET(time, FEAT_IRQC_TIME); + + return __nvme_set_features(fd, NVME_FEAT_FID_IRQ_COALESCE, value, save, + result); +} + +int nvme_set_features_irq_config(int fd, __u16 iv, bool cd, bool save, + __u32 *result) +{ + __u32 value = NVME_SET(iv, FEAT_ICFG_IV) | + NVME_SET(!!cd, FEAT_ICFG_CD); + + return __nvme_set_features(fd, NVME_FEAT_FID_IRQ_CONFIG, value, save, + result); +} + +int nvme_set_features_write_atomic(int fd, bool dn, bool save, __u32 *result) +{ + __u32 value = NVME_SET(!!dn, FEAT_WA_DN); + + return __nvme_set_features(fd, NVME_FEAT_FID_WRITE_ATOMIC, value, save, + result); +} + +int nvme_set_features_async_event(int fd, __u32 events, + bool save, __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_ASYNC_EVENT, events, save, + result); +} + +int nvme_set_features_auto_pst(int fd, bool apste, bool save, + struct nvme_feat_auto_pst *apst, __u32 *result) +{ + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_AUTO_PST, + .nsid = NVME_NSID_NONE, + .cdw11 = NVME_SET(!!apste, FEAT_APST_APSTE), + .save = save, + .uuidx = NVME_UUID_NONE, + .data = apst, + .data_len = sizeof(*apst), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_timestamp(int fd, bool save, __u64 timestamp) +{ + __le64 t = cpu_to_le64(timestamp); + struct nvme_timestamp ts = {}; + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_TIMESTAMP, + .nsid = NVME_NSID_NONE, + .cdw11 = 0, + .cdw12 = 0, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = sizeof(ts), + .data = &ts, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + memcpy(ts.timestamp, &t, sizeof(ts.timestamp)); + return nvme_set_features(&args); +} + +int nvme_set_features_hctm(int fd, __u16 tmt2, __u16 tmt1, + bool save, __u32 *result) +{ + __u32 value = NVME_SET(tmt2, FEAT_HCTM_TMT2) | + NVME_SET(tmt1, FEAT_HCTM_TMT1); + + return __nvme_set_features(fd, NVME_FEAT_FID_HCTM, value, save, + result); +} + +int nvme_set_features_nopsc(int fd, bool noppme, bool save, __u32 *result) +{ + __u32 value = NVME_SET(noppme, FEAT_NOPS_NOPPME); + + return __nvme_set_features(fd, NVME_FEAT_FID_NOPSC, value, save, + result); +} + +int nvme_set_features_rrl(int fd, __u8 rrl, __u16 nvmsetid, + bool save, __u32 *result) +{ + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_RRL, + .nsid = NVME_NSID_NONE, + .cdw11 = nvmsetid, + .cdw12 = rrl, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_plm_config(int fd, bool plm, __u16 nvmsetid, bool save, + struct nvme_plm_config *data, __u32 *result) +{ + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_PLM_CONFIG, + .nsid = NVME_NSID_NONE, + .cdw11 = nvmsetid, + .cdw12 = !!plm, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = sizeof(*data), + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_plm_window(int fd, enum nvme_feat_plm_window_select sel, + __u16 nvmsetid, bool save, __u32 *result) +{ + __u32 cdw12 = NVME_SET(sel, FEAT_PLMW_WS); + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_PLM_WINDOW, + .nsid = NVME_NSID_NONE, + .cdw11 = nvmsetid, + .cdw12 = cdw12, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_lba_sts_interval(int fd, __u16 lsiri, __u16 lsipi, + bool save, __u32 *result) +{ + __u32 value = NVME_SET(lsiri, FEAT_LBAS_LSIRI) | + NVME_SET(lsipi, FEAT_LBAS_LSIPI); + + return __nvme_set_features(fd, NVME_FEAT_FID_LBA_STS_INTERVAL, value, + save, result); +} + +int nvme_set_features_host_behavior(int fd, bool save, + struct nvme_feat_host_behavior *data) +{ + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_HOST_BEHAVIOR, + .nsid = NVME_NSID_NONE, + .cdw11 = 0, + .cdw12 = 0, + .save = false, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = sizeof(*data), + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_sanitize(int fd, bool nodrm, bool save, __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_SANITIZE, !!nodrm, save, + result); +} + +int nvme_set_features_endurance_evt_cfg(int fd, __u16 endgid, __u8 egwarn, + bool save, __u32 *result) +{ + __u32 value = endgid | egwarn << 16; + + return __nvme_set_features(fd, NVME_FEAT_FID_ENDURANCE_EVT_CFG, value, + save, result); +} + +int nvme_set_features_sw_progress(int fd, __u8 pbslc, bool save, + __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_SW_PROGRESS, pbslc, save, + result); +} + +int nvme_set_features_host_id(int fd, bool exhid, bool save, __u8 *hostid) +{ + __u32 len = exhid ? 16 : 8; + __u32 value = !!exhid; + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_HOST_ID, + .nsid = NVME_NSID_NONE, + .cdw11 = value, + .cdw12 = 0, + .save = save, + .uuidx = NVME_UUID_NONE, + .cdw15 = 0, + .data_len = len, + .data = hostid, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + return nvme_set_features(&args); +} + +int nvme_set_features_resv_mask(int fd, __u32 mask, bool save, __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_RESV_MASK, mask, save, + result); +} + +int nvme_set_features_resv_mask2(int fd, __u32 nsid, __u32 mask, bool save, + __u32 *result) +{ + return nvme_set_features_simple( + fd, NVME_FEAT_FID_RESV_MASK, nsid, mask, save, result); +} + +int nvme_set_features_resv_persist(int fd, bool ptpl, bool save, __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_RESV_PERSIST, !!ptpl, save, + result); +} + +int nvme_set_features_resv_persist2(int fd, __u32 nsid, bool ptpl, bool save, + __u32 *result) +{ + return nvme_set_features_simple( + fd, NVME_FEAT_FID_RESV_PERSIST, nsid, !!ptpl, save, result); +} + +int nvme_set_features_write_protect(int fd, enum nvme_feat_nswpcfg_state state, + bool save, __u32 *result) +{ + return __nvme_set_features(fd, NVME_FEAT_FID_WRITE_PROTECT, state, + false, result); +} + +int nvme_set_features_write_protect2(int fd, __u32 nsid, + enum nvme_feat_nswpcfg_state state, + bool save, __u32 *result) +{ + return nvme_set_features_simple( + fd, NVME_FEAT_FID_WRITE_PROTECT, nsid, state, false, result); +} + +int nvme_set_features_iocs_profile(int fd, __u16 iocsi, bool save) +{ + __u32 value = NVME_SET(iocsi, FEAT_IOCSP_IOCSCI); + + return __nvme_set_features(fd, NVME_FEAT_FID_IOCS_PROFILE, value, + save, NULL); +} + +int nvme_get_features(struct nvme_get_features_args *args) +{ + __u32 cdw10 = NVME_SET(args->fid, FEATURES_CDW10_FID) | + NVME_SET(args->sel, GET_FEATURES_CDW10_SEL); + __u32 cdw14 = NVME_SET(args->uuidx, FEATURES_CDW14_UUID); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_get_features, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .cdw10 = cdw10, + .cdw11 = args->cdw11, + .cdw14 = cdw14, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +static int __nvme_get_features(int fd, enum nvme_features_id fid, + enum nvme_get_features_sel sel, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = fid, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_arbitration(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_ARBITRATION, sel, result); +} + +int nvme_get_features_power_mgmt(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_POWER_MGMT, sel, result); +} + +int nvme_get_features_lba_range(int fd, enum nvme_get_features_sel sel, + struct nvme_lba_range_type *data, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_LBA_RANGE, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = sizeof(*data), + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_lba_range2(int fd, enum nvme_get_features_sel sel, + __u32 nsid, struct nvme_lba_range_type *data, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_LBA_RANGE, + .nsid = nsid, + .sel = sel, + .uuidx = NVME_UUID_NONE, + .data = data, + .data_len = sizeof(*data), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_temp_thresh(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_TEMP_THRESH, sel, result); +} + +int nvme_get_features_err_recovery(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_ERR_RECOVERY, sel, + result); +} + +int nvme_get_features_err_recovery2(int fd, enum nvme_get_features_sel sel, + __u32 nsid, __u32 *result) +{ + + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_ERR_RECOVERY, + .nsid = nsid, + .sel = sel, + .uuidx = NVME_UUID_NONE, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_volatile_wc(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_VOLATILE_WC, sel, result); +} + +int nvme_get_features_num_queues(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_NUM_QUEUES, sel, result); +} + +int nvme_get_features_irq_coalesce(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_IRQ_COALESCE, sel, + result); +} + +int nvme_get_features_irq_config(int fd, enum nvme_get_features_sel sel, + __u16 iv, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_IRQ_CONFIG, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = iv, + .uuidx = NVME_UUID_NONE, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_write_atomic(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_WRITE_ATOMIC, sel, + result); +} + +int nvme_get_features_async_event(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_ASYNC_EVENT, sel, result); +} + +int nvme_get_features_auto_pst(int fd, enum nvme_get_features_sel sel, + struct nvme_feat_auto_pst *apst, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_AUTO_PST, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = sizeof(*apst), + .data = apst, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_host_mem_buf(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_HOST_MEM_BUF, sel, result); +} + +int nvme_get_features_host_mem_buf2(int fd, enum nvme_get_features_sel sel, + struct nvme_host_mem_buf_attrs *attrs, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_HOST_MEM_BUF, + .nsid = NVME_NSID_NONE, + .sel = sel, + .uuidx = NVME_UUID_NONE, + .data = attrs, + .data_len = sizeof(*attrs), + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_timestamp(int fd, enum nvme_get_features_sel sel, + struct nvme_timestamp *ts) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_TIMESTAMP, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = sizeof(*ts), + .data = ts, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_kato(int fd, enum nvme_get_features_sel sel, __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_KATO, sel, result); +} + +int nvme_get_features_hctm(int fd, enum nvme_get_features_sel sel, __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_HCTM, sel, result); +} + +int nvme_get_features_nopsc(int fd, enum nvme_get_features_sel sel, __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_NOPSC, sel, result); +} + +int nvme_get_features_rrl(int fd, enum nvme_get_features_sel sel, __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_RRL, sel, result); +} + +int nvme_get_features_plm_config(int fd, enum nvme_get_features_sel sel, + __u16 nvmsetid, struct nvme_plm_config *data, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_PLM_CONFIG, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = nvmsetid, + .uuidx = NVME_UUID_NONE, + .data_len = sizeof(*data), + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_plm_window(int fd, enum nvme_get_features_sel sel, + __u16 nvmsetid, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_PLM_WINDOW, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = nvmsetid, + .uuidx = NVME_UUID_NONE, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_lba_sts_interval(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_LBA_STS_INTERVAL, sel, + result); +} + +int nvme_get_features_host_behavior(int fd, enum nvme_get_features_sel sel, + struct nvme_feat_host_behavior *data, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_HOST_BEHAVIOR, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = sizeof(*data), + .data = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_sanitize(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_SANITIZE, sel, result); +} + +int nvme_get_features_endurance_event_cfg(int fd, enum nvme_get_features_sel sel, + __u16 endgid, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_ENDURANCE_EVT_CFG, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = endgid, + .uuidx = NVME_UUID_NONE, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_sw_progress(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_SW_PROGRESS, sel, result); +} + +int nvme_get_features_host_id(int fd, enum nvme_get_features_sel sel, + bool exhid, __u32 len, __u8 *hostid) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_HOST_ID, + .nsid = NVME_NSID_NONE, + .sel = sel, + .cdw11 = !!exhid, + .uuidx = NVME_UUID_NONE, + .data_len = len, + .data = hostid, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_resv_mask(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_RESV_MASK, sel, result); +} + +int nvme_get_features_resv_mask2(int fd, enum nvme_get_features_sel sel, + __u32 nsid, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_RESV_MASK, + .nsid = nsid, + .sel = sel, + .uuidx = NVME_UUID_NONE, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_resv_persist(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_RESV_PERSIST, sel, result); +} + +int nvme_get_features_resv_persist2(int fd, enum nvme_get_features_sel sel, + __u32 nsid, __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_RESV_PERSIST, + .nsid = nsid, + .sel = sel, + .uuidx = NVME_UUID_NONE, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_write_protect(int fd, __u32 nsid, + enum nvme_get_features_sel sel, + __u32 *result) +{ + struct nvme_get_features_args args = { + .args_size = sizeof(args), + .fd = fd, + .fid = NVME_FEAT_FID_WRITE_PROTECT, + .nsid = nsid, + .sel = sel, + .cdw11 = 0, + .uuidx = NVME_UUID_NONE, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = result, + }; + + return nvme_get_features(&args); +} + +int nvme_get_features_iocs_profile(int fd, enum nvme_get_features_sel sel, + __u32 *result) +{ + return __nvme_get_features(fd, NVME_FEAT_FID_IOCS_PROFILE, sel, result); +} + +int nvme_format_nvm(struct nvme_format_nvm_args *args) +{ + const size_t size_v1 = sizeof_args(struct nvme_format_nvm_args, lbaf, __u64); + const size_t size_v2 = sizeof_args(struct nvme_format_nvm_args, lbafu, __u64); + __u32 cdw10; + + if (args->args_size < size_v1 || args->args_size > size_v2) { + errno = EINVAL; + return -1; + } + + cdw10 = NVME_SET(args->lbaf, FORMAT_CDW10_LBAF) | + NVME_SET(args->mset, FORMAT_CDW10_MSET) | + NVME_SET(args->pi, FORMAT_CDW10_PI) | + NVME_SET(args->pil, FORMAT_CDW10_PIL) | + NVME_SET(args->ses, FORMAT_CDW10_SES); + + if (args->args_size == size_v2) { + /* set lbafu extension */ + cdw10 |= NVME_SET(args->lbafu, FORMAT_CDW10_LBAFU); + } + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_format_nvm, + .nsid = args->nsid, + .cdw10 = cdw10, + .timeout_ms = args->timeout, + }; + + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_ns_mgmt(struct nvme_ns_mgmt_args *args) +{ + const size_t size_v1 = sizeof_args(struct nvme_ns_mgmt_args, csi, __u64); + const size_t size_v2 = sizeof_args(struct nvme_ns_mgmt_args, data, __u64); + __u32 cdw10 = NVME_SET(args->sel, NAMESPACE_MGMT_CDW10_SEL); + __u32 cdw11 = NVME_SET(args->csi, NAMESPACE_MGMT_CDW11_CSI); + + if (args->args_size < size_v1 || args->args_size > size_v2) { + errno = EINVAL; + return -1; + } + + struct nvme_passthru_cmd cmd = { + .nsid = args->nsid, + .opcode = nvme_admin_ns_mgmt, + .cdw10 = cdw10, + .cdw11 = cdw11, + .timeout_ms = args->timeout, + }; + + if (args->args_size == size_v2) { + if (args->data) { + cmd.data_len = sizeof(*args->data); + cmd.addr = (__u64)(uintptr_t)args->data; + } + } + else { + if (args->ns) { + cmd.data_len = sizeof(*args->ns); + cmd.addr = (__u64)(uintptr_t)args->ns; + } + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_ns_attach(struct nvme_ns_attach_args *args) +{ + __u32 cdw10 = NVME_SET(args->sel, NAMESPACE_ATTACH_CDW10_SEL); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_ns_attach, + .nsid = args->nsid, + .cdw10 = cdw10, + .data_len = sizeof(*args->ctrlist), + .addr = (__u64)(uintptr_t)args->ctrlist, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_fw_download(struct nvme_fw_download_args *args) +{ + __u32 cdw10 = (args->data_len >> 2) - 1; + __u32 cdw11 = args->offset >> 2; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fw_download, + .cdw10 = cdw10, + .cdw11 = cdw11, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_fw_commit(struct nvme_fw_commit_args *args) +{ + __u32 cdw10 = NVME_SET(args->slot, FW_COMMIT_CDW10_FS) | + NVME_SET(args->action, FW_COMMIT_CDW10_CA) | + NVME_SET(args->bpid, FW_COMMIT_CDW10_BPID); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fw_commit, + .cdw10 = cdw10, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_security_send(struct nvme_security_send_args *args) +{ + __u32 cdw10 = NVME_SET(args->secp, SECURITY_SECP) | + NVME_SET(args->spsp0, SECURITY_SPSP0) | + NVME_SET(args->spsp1, SECURITY_SPSP1) | + NVME_SET(args->nssf, SECURITY_NSSF); + __u32 cdw11 = args->tl; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_security_send, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_security_receive(struct nvme_security_receive_args *args) +{ + __u32 cdw10 = NVME_SET(args->secp, SECURITY_SECP) | + NVME_SET(args->spsp0, SECURITY_SPSP0) | + NVME_SET(args->spsp1, SECURITY_SPSP1) | + NVME_SET(args->nssf, SECURITY_NSSF); + __u32 cdw11 = args->al; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_security_recv, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_get_lba_status(struct nvme_get_lba_status_args *args) +{ + __u32 cdw10 = args->slba & 0xffffffff; + __u32 cdw11 = args->slba >> 32; + __u32 cdw12 = args->mndw; + __u32 cdw13 = NVME_SET(args->rl, GET_LBA_STATUS_CDW13_RL) | + NVME_SET(args->atype, GET_LBA_STATUS_CDW13_ATYPE); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_get_lba_status, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->lbas, + .data_len = (args->mndw + 1) << 2, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_directive_send(struct nvme_directive_send_args *args) +{ + __u32 cdw10 = args->data_len ? (args->data_len >> 2) - 1 : 0; + __u32 cdw11 = NVME_SET(args->doper, DIRECTIVE_CDW11_DOPER) | + NVME_SET(args->dtype, DIRECTIVE_CDW11_DTYPE) | + NVME_SET(args->dspec, DIRECTIVE_CDW11_DPSEC); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_directive_send, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = args->cdw12, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_directive_send_id_endir(int fd, __u32 nsid, bool endir, + enum nvme_directive_dtype dtype, + struct nvme_id_directives *id) +{ + __u32 cdw12 = NVME_SET(dtype, DIRECTIVE_SEND_IDENTIFY_CDW12_DTYPE) | + NVME_SET(endir, DIRECTIVE_SEND_IDENTIFY_CDW12_ENDIR); + struct nvme_directive_send_args args = { + .args_size = sizeof(args), + .fd = fd, + .nsid = nsid, + .dspec = 0, + .dtype = NVME_DIRECTIVE_DTYPE_IDENTIFY, + .doper = NVME_DIRECTIVE_SEND_IDENTIFY_DOPER_ENDIR, + .cdw12 = cdw12, + .data_len = sizeof(*id), + .data = id, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + + return nvme_directive_send(&args); +} + +int nvme_directive_recv(struct nvme_directive_recv_args *args) +{ + __u32 cdw10 = args->data_len ? (args->data_len >> 2) - 1 : 0; + __u32 cdw11 = NVME_SET(args->doper, DIRECTIVE_CDW11_DOPER) | + NVME_SET(args->dtype, DIRECTIVE_CDW11_DTYPE) | + NVME_SET(args->dspec, DIRECTIVE_CDW11_DPSEC); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_directive_recv, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = args->cdw12, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_capacity_mgmt(struct nvme_capacity_mgmt_args *args) +{ + __u32 cdw10 = args->op | args->element_id << 16; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_capacity_mgmt, + .cdw10 = cdw10, + .cdw11 = args->cdw11, + .cdw12 = args->cdw12, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_lockdown(struct nvme_lockdown_args *args) +{ + __u32 cdw10 = args->ofi << 8 | + (args->ifc & 0x3) << 5 | + (args->prhbt & 0x1) << 4 | + (args->scp & 0xF); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_lockdown, + .cdw10 = cdw10, + .cdw14 = args->uuidx & 0x3F, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_set_property(struct nvme_set_property_args *args) +{ + __u32 cdw10 = nvme_is_64bit_reg(args->offset); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fabrics, + .nsid = nvme_fabrics_type_property_set, + .cdw10 = cdw10, + .cdw11 = args->offset, + .cdw12 = args->value & 0xffffffff, + .cdw13 = args->value >> 32, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_get_property(struct nvme_get_property_args *args) +{ + __u32 cdw10 = nvme_is_64bit_reg(args->offset); + + struct nvme_passthru_cmd64 cmd = { + .opcode = nvme_admin_fabrics, + .nsid = nvme_fabrics_type_property_get, + .cdw10 = cdw10, + .cdw11 = args->offset, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru64(args->fd, &cmd, args->value); +} + +int nvme_sanitize_nvm(struct nvme_sanitize_nvm_args *args) +{ + __u32 cdw10 = NVME_SET(args->sanact, SANITIZE_CDW10_SANACT) | + NVME_SET(!!args->ause, SANITIZE_CDW10_AUSE) | + NVME_SET(args->owpass, SANITIZE_CDW10_OWPASS) | + NVME_SET(!!args->oipbp, SANITIZE_CDW10_OIPBP) | + NVME_SET(!!args->nodas, SANITIZE_CDW10_NODAS); + __u32 cdw11 = args->ovrpat; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_sanitize_nvm, + .cdw10 = cdw10, + .cdw11 = cdw11, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_dev_self_test(struct nvme_dev_self_test_args *args) +{ + __u32 cdw10 = NVME_SET(args->stc, DEVICE_SELF_TEST_CDW10_STC); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_dev_self_test, + .nsid = args->nsid, + .cdw10 = cdw10, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_virtual_mgmt(struct nvme_virtual_mgmt_args *args) +{ + __u32 cdw10 = NVME_SET(args->act, VIRT_MGMT_CDW10_ACT) | + NVME_SET(args->rt, VIRT_MGMT_CDW10_RT) | + NVME_SET(args->cntlid, VIRT_MGMT_CDW10_CNTLID); + __u32 cdw11 = NVME_SET(args->nr, VIRT_MGMT_CDW11_NR); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_virtual_mgmt, + .cdw10 = cdw10, + .cdw11 = cdw11, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} + +int nvme_submit_io_passthru64(int fd, struct nvme_passthru_cmd64 *cmd, + __u64 *result) +{ + return nvme_submit_passthru64(fd, NVME_IOCTL_IO64_CMD, cmd, result); +} + +int nvme_io_passthru64(int fd, __u8 opcode, __u8 flags, __u16 rsvd, + __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, + __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, + __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, + void *metadata, __u32 timeout_ms, __u64 *result) +{ + return nvme_passthru64(fd, NVME_IOCTL_IO64_CMD, opcode, flags, rsvd, + nsid, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, + cdw14, cdw15, data_len, data, metadata_len, metadata, + timeout_ms, result); +} + +int nvme_submit_io_passthru(int fd, struct nvme_passthru_cmd *cmd, __u32 *result) +{ + return nvme_submit_passthru(fd, NVME_IOCTL_IO_CMD, cmd, result); +} + +int nvme_io_passthru(int fd, __u8 opcode, __u8 flags, __u16 rsvd, + __u32 nsid, __u32 cdw2, __u32 cdw3, __u32 cdw10, + __u32 cdw11, __u32 cdw12, __u32 cdw13, __u32 cdw14, + __u32 cdw15, __u32 data_len, void *data, __u32 metadata_len, + void *metadata, __u32 timeout_ms, __u32 *result) +{ + return nvme_passthru(fd, NVME_IOCTL_IO_CMD, opcode, flags, rsvd, nsid, + cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, cdw14, + cdw15, data_len, data, metadata_len, metadata, + timeout_ms, result); +} + +static int nvme_set_var_size_tags(__u32 *cmd_dw2, __u32 *cmd_dw3, __u32 *cmd_dw14, + __u8 pif, __u8 sts, __u64 reftag, __u64 storage_tag) +{ + __u32 cdw2 = 0, cdw3 = 0, cdw14; + beint64_t be_reftag = cpu_to_be64(reftag); + beint64_t be_storage_tag = cpu_to_be64(storage_tag); + + switch (pif) { + /* 16b Protection Information */ + case 0: + cdw14 = be_reftag & 0xffffffff; + cdw14 |= ((be_storage_tag << (32 - sts)) & 0xffffffff); + break; + /* 32b Protection Information */ + case 1: + cdw14 = be_reftag & 0xffffffff; + cdw3 = be_reftag >> 32; + cdw14 |= ((be_storage_tag << (80 - sts)) & 0xffff0000); + if (sts >= 48) + cdw3 |= ((be_storage_tag >> (sts - 48)) & 0xffffffff); + else + cdw3 |= ((be_storage_tag << (48 - sts)) & 0xffffffff); + cdw2 = (be_storage_tag >> (sts - 16)) & 0xffff; + break; + /* 64b Protection Information */ + case 2: + cdw14 = be_reftag & 0xffffffff; + cdw3 = (be_reftag >> 32) & 0xffff; + cdw14 |= ((be_storage_tag << (48 - sts)) & 0xffffffff); + if (sts >= 16) + cdw3 |= ((be_storage_tag >> (sts - 16)) & 0xffff); + else + cdw3 |= ((be_storage_tag << (16 - sts)) & 0xffff); + break; + default: + perror("Unsupported Protection Information Format"); + errno = EINVAL; + return -1; + } + + *cmd_dw2 = cdw2; + *cmd_dw3 = cdw3; + *cmd_dw14 = cdw14; + return 0; +} + +int nvme_io(struct nvme_io_args *args, __u8 opcode) +{ + const size_t size_v1 = sizeof_args(struct nvme_io_args, dsm, __u64); + const size_t size_v2 = sizeof_args(struct nvme_io_args, pif, __u64); + __u32 cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, cdw14, cdw15; + + if (args->args_size < size_v1 || args->args_size > size_v2) { + errno = EINVAL; + return -1; + } + + cdw10 = args->slba & 0xffffffff; + cdw11 = args->slba >> 32; + cdw12 = args->nlb | (args->control << 16); + cdw13 = args->dsm | (args->dspec << 16); + cdw15 = args->apptag | (args->appmask << 16); + + if (args->args_size == size_v1) { + cdw2 = (args->storage_tag >> 32) & 0xffff; + cdw3 = args->storage_tag & 0xffffffff; + cdw14 = args->reftag; + } else { + if (nvme_set_var_size_tags(&cdw2, &cdw3, &cdw14, + args->pif, + args->sts, + args->reftag_u64, + args->storage_tag)) { + errno = EINVAL; + return -1; + } + } + + struct nvme_passthru_cmd cmd = { + .opcode = opcode, + .nsid = args->nsid, + .cdw2 = cdw2, + .cdw3 = cdw3, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .cdw14 = cdw14, + .cdw15 = cdw15, + .data_len = args->data_len, + .metadata_len = args->metadata_len, + .addr = (__u64)(uintptr_t)args->data, + .metadata = (__u64)(uintptr_t)args->metadata, + .timeout_ms = args->timeout, + }; + + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_dsm(struct nvme_dsm_args *args) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_dsm, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->dsm, + .data_len = args->nr_ranges * sizeof(*args->dsm), + .cdw10 = args->nr_ranges - 1, + .cdw11 = args->attrs, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_copy(struct nvme_copy_args *args) +{ + const size_t size_v1 = sizeof_args(struct nvme_copy_args, format, __u64); + const size_t size_v2 = sizeof_args(struct nvme_copy_args, ilbrt_u64, __u64); + __u32 cdw3, cdw12, cdw14, data_len; + + if (args->args_size < size_v1 || args->args_size > size_v2) { + errno = EINVAL; + return -1; + } + + cdw12 = ((args->nr - 1) & 0xff) | ((args->format & 0xf) << 8) | + ((args->prinfor & 0xf) << 12) | ((args->dtype & 0xf) << 20) | + ((args->prinfow & 0xf) << 26) | ((args->fua & 0x1) << 30) | + ((args->lr & 0x1) << 31); + + if (args->args_size == size_v1) { + cdw3 = 0; + cdw14 = args->ilbrt; + } else { + cdw3 = (args->ilbrt_u64 >> 32) & 0xffffffff; + cdw14 = args->ilbrt_u64 & 0xffffffff; + } + + if (args->format == 1) + data_len = args->nr * sizeof(struct nvme_copy_range_f1); + else if (args->format == 2) + data_len = args->nr * sizeof(struct nvme_copy_range_f2); + else if (args->format == 3) + data_len = args->nr * sizeof(struct nvme_copy_range_f3); + else + data_len = args->nr * sizeof(struct nvme_copy_range); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_copy, + .nsid = args->nsid, + .addr = (__u64)(uintptr_t)args->copy, + .data_len = data_len, + .cdw3 = cdw3, + .cdw10 = args->sdlba & 0xffffffff, + .cdw11 = args->sdlba >> 32, + .cdw12 = cdw12, + .cdw13 = (args->dspec & 0xffff) << 16, + .cdw14 = cdw14, + .cdw15 = (args->lbatm << 16) | args->lbat, + .timeout_ms = args->timeout, + }; + + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_resv_acquire(struct nvme_resv_acquire_args *args) +{ + __le64 payload[2] = { + cpu_to_le64(args->crkey), + cpu_to_le64(args->nrkey) + }; + __u32 cdw10 = (args->racqa & 0x7) | + (args->iekey ? 1 << 3 : 0) | + (args->rtype << 8); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_resv_acquire, + .nsid = args->nsid, + .cdw10 = cdw10, + .data_len = sizeof(payload), + .addr = (__u64)(uintptr_t)(payload), + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_resv_register(struct nvme_resv_register_args *args) +{ + __le64 payload[2] = { + cpu_to_le64(args->crkey), + cpu_to_le64(args->nrkey) + }; + __u32 cdw10 = (args->rrega & 0x7) | + (args->iekey ? 1 << 3 : 0) | + (args->cptpl << 30); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_resv_register, + .nsid = args->nsid, + .cdw10 = cdw10, + .data_len = sizeof(payload), + .addr = (__u64)(uintptr_t)(payload), + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_resv_release(struct nvme_resv_release_args *args) +{ + __le64 payload[1] = { cpu_to_le64(args->crkey) }; + __u32 cdw10 = (args->rrela & 0x7) | + (args->iekey ? 1 << 3 : 0) | + (args->rtype << 8); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_resv_release, + .nsid = args->nsid, + .cdw10 = cdw10, + .addr = (__u64)(uintptr_t)(payload), + .data_len = sizeof(payload), + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_resv_report(struct nvme_resv_report_args *args) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_resv_report, + .nsid = args->nsid, + .cdw10 = (args->len >> 2) - 1, + .cdw11 = args->eds ? 1 : 0, + .addr = (__u64)(uintptr_t)args->report, + .data_len = args->len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_io_mgmt_recv(struct nvme_io_mgmt_recv_args *args) +{ + __u32 cdw10 = args->mo | (args->mos << 16); + __u32 cdw11 = (args->data_len >> 2) - 1; + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_io_mgmt_recv, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + + return nvme_submit_io_passthru(args->fd, &cmd, NULL); +} + +int nvme_io_mgmt_send(struct nvme_io_mgmt_send_args *args) +{ + __u32 cdw10 = args->mo | (args->mos << 16); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_io_mgmt_send, + .nsid = args->nsid, + .cdw10 = cdw10, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + + return nvme_submit_io_passthru(args->fd, &cmd, NULL); +} + +int nvme_zns_mgmt_send(struct nvme_zns_mgmt_send_args *args) +{ + __u32 cdw10 = args->slba & 0xffffffff; + __u32 cdw11 = args->slba >> 32; + __u32 cdw13 = NVME_SET(args->zsaso, ZNS_MGMT_SEND_ZSASO) | + NVME_SET(!!args->select_all, ZNS_MGMT_SEND_SEL) | + NVME_SET(args->zsa, ZNS_MGMT_SEND_ZSA); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_zns_cmd_mgmt_send, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw13 = cdw13, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_zns_mgmt_recv(struct nvme_zns_mgmt_recv_args *args) +{ + __u32 cdw10 = args->slba & 0xffffffff; + __u32 cdw11 = args->slba >> 32; + __u32 cdw12 = (args->data_len >> 2) - 1; + __u32 cdw13 = NVME_SET(args->zra, ZNS_MGMT_RECV_ZRA) | + NVME_SET(args->zrasf, ZNS_MGMT_RECV_ZRASF) | + NVME_SET(args->zras_feat, ZNS_MGMT_RECV_ZRAS_FEAT); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_zns_cmd_mgmt_recv, + .nsid = args->nsid, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw13 = cdw13, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + return nvme_submit_io_passthru(args->fd, &cmd, args->result); +} + +int nvme_zns_append(struct nvme_zns_append_args *args) +{ + const size_t size_v1 = sizeof_args(struct nvme_zns_append_args, lbatm, __u64); + const size_t size_v2 = sizeof_args(struct nvme_zns_append_args, ilbrt_u64, __u64); + __u32 cdw3, cdw10, cdw11, cdw12, cdw14, cdw15; + + if (args->args_size < size_v1 || args->args_size > size_v2) { + errno = EINVAL; + return -1; + } + + cdw10 = args->zslba & 0xffffffff; + cdw11 = args->zslba >> 32; + cdw12 = args->nlb | (args->control << 16); + cdw15 = args->lbat | (args->lbatm << 16); + + if (args->args_size == size_v1) { + cdw3 = 0; + cdw14 = args->ilbrt; + } else { + cdw3 = (args->ilbrt_u64 >> 32) & 0xffffffff; + cdw14 = args->ilbrt_u64 & 0xffffffff; + } + + struct nvme_passthru_cmd64 cmd = { + .opcode = nvme_zns_cmd_append, + .nsid = args->nsid, + .cdw3 = cdw3, + .cdw10 = cdw10, + .cdw11 = cdw11, + .cdw12 = cdw12, + .cdw14 = cdw14, + .cdw15 = cdw15, + .data_len = args->data_len, + .addr = (__u64)(uintptr_t)args->data, + .metadata_len = args->metadata_len, + .metadata = (__u64)(uintptr_t)args->metadata, + .timeout_ms = args->timeout, + }; + + return nvme_submit_io_passthru64(args->fd, &cmd, args->result); +} + +int nvme_dim_send(struct nvme_dim_args *args) +{ + __u32 cdw10 = NVME_SET(args->tas, DIM_TAS); + + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_discovery_info_mgmt, + .cdw10 = cdw10, + .addr = (__u64)(uintptr_t)args->data, + .data_len = args->data_len, + .timeout_ms = args->timeout, + }; + + if (args->args_size < sizeof(*args)) { + errno = EINVAL; + return -1; + } + + return nvme_submit_admin_passthru(args->fd, &cmd, args->result); +} |