summaryrefslogtreecommitdiffstats
path: root/src/nvme/linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvme/linux.c')
-rw-r--r--src/nvme/linux.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/src/nvme/linux.c b/src/nvme/linux.c
new file mode 100644
index 0000000..c9b6bbf
--- /dev/null
+++ b/src/nvme/linux.c
@@ -0,0 +1,679 @@
+// 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef CONFIG_OPENSSL
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+#ifdef CONFIG_OPENSSL_3
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#endif
+#endif
+
+#include <ccan/endian/endian.h>
+
+#include "linux.h"
+#include "tree.h"
+#include "log.h"
+#include "private.h"
+
+static int __nvme_open(const char *name)
+{
+ char *path;
+ int fd, ret;
+
+ ret = asprintf(&path, "%s/%s", "/dev", name);
+ if (ret < 0) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ fd = open(path, O_RDONLY);
+ free(path);
+ return fd;
+}
+
+int nvme_open(const char *name)
+{
+ int ret, fd, id, ns;
+ struct stat stat;
+ bool c;
+
+ ret = sscanf(name, "nvme%dn%d", &id, &ns);
+ if (ret != 1 && ret != 2) {
+ errno = EINVAL;
+ return -1;
+ }
+ c = ret == 1;
+
+ fd = __nvme_open(name);
+ if (fd < 0)
+ return fd;
+
+ ret = fstat(fd, &stat);
+ if (ret < 0)
+ goto close_fd;
+
+ if (c) {
+ if (!S_ISCHR(stat.st_mode)) {
+ errno = EINVAL;
+ goto close_fd;
+ }
+ } else if (!S_ISBLK(stat.st_mode)) {
+ errno = EINVAL;
+ goto close_fd;
+ }
+
+ return fd;
+
+close_fd:
+ close(fd);
+ return -1;
+}
+
+int nvme_fw_download_seq(int fd, __u32 size, __u32 xfer, __u32 offset,
+ void *buf)
+{
+ int err = 0;
+ struct nvme_fw_download_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .offset = offset,
+ .data_len = xfer,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+
+ while (size > 0) {
+ args.data_len = MIN(xfer, size);
+ err = nvme_fw_download(&args);
+ if (err)
+ break;
+
+ args.data += xfer;
+ size -= xfer;
+ args.offset += xfer;
+ }
+
+ return err;
+}
+
+int nvme_get_log_page(int fd, __u32 xfer_len, struct nvme_get_log_args *args)
+{
+ __u64 offset = 0, xfer, data_len = args->len;
+ bool retain = true;
+ void *ptr = args->log;
+ int ret;
+
+ /*
+ * 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.
+ */
+ if (offset + xfer == data_len)
+ retain = args->rae;
+
+ args->lpo = offset;
+ args->len = xfer;
+ args->log = ptr;
+ args->rae = retain;
+ ret = nvme_get_log(args);
+ if (ret)
+ return ret;
+
+ offset += xfer;
+ ptr += xfer;
+ } while (offset < data_len);
+
+ return 0;
+}
+
+static int nvme_get_telemetry_log(int fd, bool create, bool ctrl, bool rae,
+ struct nvme_telemetry_log **buf, enum nvme_telemetry_da da,
+ size_t *size)
+{
+ static const __u32 xfer = NVME_LOG_TELEM_BLOCK_SIZE;
+
+ struct nvme_telemetry_log *telem;
+ enum nvme_cmd_get_log_lid lid;
+ struct nvme_id_ctrl id_ctrl;
+ void *log, *tmp;
+ int err;
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = NVME_NSID_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = NVME_LOG_LSI_NONE,
+ .uuidx = NVME_UUID_NONE,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ .csi = NVME_CSI_NVM,
+ .rae = rae,
+ .ot = false,
+ };
+
+ *size = 0;
+
+ log = malloc(xfer);
+ if (!log) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (ctrl) {
+ err = nvme_get_log_telemetry_ctrl(fd, true, 0, xfer, log);
+ lid = NVME_LOG_LID_TELEMETRY_CTRL;
+ } else {
+ lid = NVME_LOG_LID_TELEMETRY_HOST;
+ if (create)
+ err = nvme_get_log_create_telemetry_host(fd, log);
+ else
+ err = nvme_get_log_telemetry_host(fd, 0, xfer, log);
+ }
+
+ if (err)
+ goto free;
+
+ telem = log;
+ if (ctrl && !telem->ctrlavail) {
+ *buf = log;
+ *size = xfer;
+ return 0;
+ }
+
+ switch (da) {
+ case NVME_TELEMETRY_DA_1:
+ case NVME_TELEMETRY_DA_2:
+ case NVME_TELEMETRY_DA_3:
+ /* dalb3 >= dalb2 >= dalb1 */
+ *size = (le16_to_cpu(telem->dalb3) + 1) * xfer;
+ break;
+ case NVME_TELEMETRY_DA_4:
+ err = nvme_identify_ctrl(fd, &id_ctrl);
+ if (err) {
+ perror("identify-ctrl");
+ errno = EINVAL;
+ goto free;
+ }
+
+ if (id_ctrl.lpa & 0x40) {
+ *size = (le32_to_cpu(telem->dalb4) + 1) * xfer;
+ } else {
+ fprintf(stderr, "Data area 4 unsupported, bit 6 of Log Page Attributes not set\n");
+ errno = EINVAL;
+ err = -1;
+ goto free;
+ }
+ break;
+ default:
+ fprintf(stderr, "Invalid data area parameter - %d\n", da);
+ errno = EINVAL;
+ err = -1;
+ goto free;
+ }
+
+ tmp = realloc(log, *size);
+ if (!tmp) {
+ errno = ENOMEM;
+ err = -1;
+ goto free;
+ }
+ log = tmp;
+
+ args.lid = lid;
+ args.log = log;
+ args.len = *size;
+ err = nvme_get_log_page(fd, 4096, &args);
+ if (!err) {
+ *buf = log;
+ return 0;
+ }
+free:
+ free(log);
+ return err;
+}
+
+int nvme_get_ctrl_telemetry(int fd, bool rae, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_get_telemetry_log(fd, false, true, rae, log, da, size);
+}
+
+int nvme_get_host_telemetry(int fd, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_get_telemetry_log(fd, false, false, false, log, da, size);
+}
+
+int nvme_get_new_host_telemetry(int fd, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_get_telemetry_log(fd, true, false, false, log, da, size);
+}
+
+int nvme_get_lba_status_log(int fd, bool rae, struct nvme_lba_status_log **log)
+{
+ __u32 size = sizeof(struct nvme_lba_status_log);
+ void *buf, *tmp;
+ int err;
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = NVME_NSID_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = NVME_LOG_LSI_NONE,
+ .uuidx = NVME_UUID_NONE,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ .csi = NVME_CSI_NVM,
+ .rae = rae,
+ .ot = false,
+ };
+
+ buf = malloc(size);
+ if (!buf)
+ return -1;
+
+ *log = buf;
+ err = nvme_get_log_lba_status(fd, true, 0, size, buf);
+ if (err)
+ goto free;
+
+ size = le32_to_cpu((*log)->lslplen);
+ if (!size)
+ return 0;
+
+ tmp = realloc(buf, size);
+ if (!tmp) {
+ err = -1;
+ goto free;
+ }
+ buf = tmp;
+ *log = buf;
+
+ args.lid = NVME_LOG_LID_LBA_STATUS;
+ args.log = buf;
+ args.len = size;
+ err = nvme_get_log_page(fd, 4096, &args);
+ if (!err)
+ return 0;
+
+free:
+ *log = NULL;
+ free(buf);
+ return err;
+}
+
+static int nvme_ns_attachment(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist, bool attach, __u32 timeout)
+{
+ struct nvme_ctrl_list cntlist = { 0 };
+ struct nvme_ns_attach_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = nsid,
+ .sel = NVME_NS_ATTACH_SEL_CTRL_DEATTACH,
+ .ctrlist = &cntlist,
+ .timeout = timeout,
+ };
+
+ if (attach)
+ args.sel = NVME_NS_ATTACH_SEL_CTRL_ATTACH;
+
+ nvme_init_ctrl_list(args.ctrlist, num_ctrls, ctrlist);
+ return nvme_ns_attach(&args);
+}
+
+int nvme_namespace_attach_ctrls(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist)
+{
+ return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, true,
+ NVME_DEFAULT_IOCTL_TIMEOUT);
+}
+
+int nvme_namespace_detach_ctrls(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist)
+{
+ return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, false,
+ NVME_DEFAULT_IOCTL_TIMEOUT);
+}
+
+int nvme_get_ana_log_len(int fd, size_t *analen)
+{
+ struct nvme_id_ctrl ctrl;
+ int ret;
+
+ ret = nvme_identify_ctrl(fd, &ctrl);
+ if (ret)
+ return ret;
+
+ *analen = sizeof(struct nvme_ana_log) +
+ le32_to_cpu(ctrl.nanagrpid) * sizeof(struct nvme_ana_group_desc) +
+ le32_to_cpu(ctrl.mnan) * sizeof(__le32);
+ return 0;
+}
+
+int nvme_get_logical_block_size(int fd, __u32 nsid, int *blksize)
+{
+ struct nvme_id_ns ns;
+ __u8 flbas;
+ int ret;
+
+ ret = nvme_identify_ns(fd, nsid, &ns);
+ if (ret)
+ return ret;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbas);
+ *blksize = 1 << ns.lbaf[flbas].ds;
+
+ return 0;
+}
+
+static int __nvme_set_attr(const char *path, const char *value)
+{
+ int ret, fd;
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0) {
+#if 0
+ nvme_msg(LOG_DEBUG, "Failed to open %s: %s\n", path,
+ strerror(errno));
+#endif
+ return -1;
+ }
+ ret = write(fd, value, strlen(value));
+ close(fd);
+ return ret;
+}
+
+int nvme_set_attr(const char *dir, const char *attr, const char *value)
+{
+ char *path;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", dir, attr);
+ if (ret < 0)
+ return -1;
+
+ ret = __nvme_set_attr(path, value);
+ free(path);
+ return ret;
+}
+
+static char *__nvme_get_attr(const char *path)
+{
+ char value[4096] = { 0 };
+ int ret, fd;
+ int saved_errno;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = read(fd, value, sizeof(value) - 1);
+ saved_errno = errno;
+ close(fd);
+ if (ret < 0) {
+ errno = saved_errno;
+ return NULL;
+ }
+ errno = 0;
+ if (!strlen(value))
+ return NULL;
+
+ if (value[strlen(value) - 1] == '\n')
+ value[strlen(value) - 1] = '\0';
+ while (strlen(value) > 0 && value[strlen(value) - 1] == ' ')
+ value[strlen(value) - 1] = '\0';
+
+ return strlen(value) ? strdup(value) : NULL;
+}
+
+char *nvme_get_attr(const char *dir, const char *attr)
+{
+ char *path, *value;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", dir, attr);
+ if (ret < 0) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ value = __nvme_get_attr(path);
+ free(path);
+ return value;
+}
+
+char *nvme_get_subsys_attr(nvme_subsystem_t s, const char *attr)
+{
+ return nvme_get_attr(nvme_subsystem_get_sysfs_dir(s), attr);
+}
+
+char *nvme_get_ctrl_attr(nvme_ctrl_t c, const char *attr)
+{
+ return nvme_get_attr(nvme_ctrl_get_sysfs_dir(c), attr);
+}
+
+char *nvme_get_ns_attr(nvme_ns_t n, const char *attr)
+{
+ return nvme_get_attr(nvme_ns_get_sysfs_dir(n), attr);
+}
+
+char *nvme_get_path_attr(nvme_path_t p, const char *attr)
+{
+ return nvme_get_attr(nvme_path_get_sysfs_dir(p), attr);
+}
+
+#ifndef CONFIG_OPENSSL
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ if (hmac != NVME_HMAC_ALG_NONE) {
+ nvme_msg(NULL, LOG_ERR, "HMAC transformation not supported; " \
+ "recompile with OpenSSL support.\n");
+ errno = -EINVAL;
+ return -1;
+ }
+
+ memcpy(key, secret, key_len);
+ return 0;
+}
+#endif /* !CONFIG_OPENSSL */
+
+#ifdef CONFIG_OPENSSL_1
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ const char hmac_seed[] = "NVMe-over-Fabrics";
+ HMAC_CTX *hmac_ctx;
+ const EVP_MD *md;
+ int err = -1;
+
+ ENGINE_load_builtin_engines();
+ ENGINE_register_all_complete();
+
+ hmac_ctx = HMAC_CTX_new();
+ if (!hmac_ctx) {
+ errno = ENOMEM;
+ return err;
+ }
+
+ switch (hmac) {
+ case NVME_HMAC_ALG_NONE:
+ memcpy(key, secret, key_len);
+ err = 0;
+ goto out;
+ case NVME_HMAC_ALG_SHA2_256:
+ md = EVP_sha256();
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ md = EVP_sha384();
+ break;
+ case NVME_HMAC_ALG_SHA2_512:
+ md = EVP_sha512();
+ break;
+ default:
+ errno = EINVAL;
+ goto out;
+ }
+
+ if (!md) {
+ errno = EINVAL;
+ goto out;
+ }
+
+ if (!HMAC_Init_ex(hmac_ctx, secret, key_len, md, NULL)) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (!HMAC_Final(hmac_ctx, key, &key_len)) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ err = 0;
+
+out:
+ HMAC_CTX_free(hmac_ctx);
+ return err;
+}
+#endif /* !CONFIG_OPENSSL_1 */
+
+#ifdef CONFIG_OPENSSL_3
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ const char hmac_seed[] = "NVMe-over-Fabrics";
+ OSSL_PARAM params[2], *p = params;
+ OSSL_LIB_CTX *lib_ctx;
+ EVP_MAC_CTX *mac_ctx = NULL;
+ EVP_MAC *mac = NULL;
+ char *progq = NULL;
+ char *digest;
+ size_t len;
+ int err = -1;
+
+ lib_ctx = OSSL_LIB_CTX_new();
+ if (!lib_ctx) {
+ errno = ENOMEM;
+ return err;
+ }
+
+ mac = EVP_MAC_fetch(lib_ctx, OSSL_MAC_NAME_HMAC, progq);
+ if (!mac) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ mac_ctx = EVP_MAC_CTX_new(mac);
+ if (!mac_ctx) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ switch (hmac) {
+ case NVME_HMAC_ALG_NONE:
+ memcpy(key, secret, key_len);
+ err = 0;
+ goto out;
+ case NVME_HMAC_ALG_SHA2_256:
+ digest = OSSL_DIGEST_NAME_SHA2_256;
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ digest = OSSL_DIGEST_NAME_SHA2_384;
+ break;
+ case NVME_HMAC_ALG_SHA2_512:
+ digest = OSSL_DIGEST_NAME_SHA2_512;
+ break;
+ default:
+ errno = EINVAL;
+ goto out;
+ }
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ digest,
+ 0);
+ *p = OSSL_PARAM_construct_end();
+
+ if (!EVP_MAC_init(mac_ctx, secret, key_len, params)) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (!EVP_MAC_final(mac_ctx, key, &len, key_len)) {
+ errno = ENOKEY;
+ goto out;
+ }
+
+ if (len != key_len) {
+ errno = EMSGSIZE;
+ goto out;
+ }
+
+ err = 0;
+
+out:
+ EVP_MAC_CTX_free(mac_ctx);
+ EVP_MAC_free(mac);
+ OSSL_LIB_CTX_free(lib_ctx);
+
+ return err;
+}
+#endif /* !CONFIG_OPENSSL_3 */