Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
# SPDX-License-Identifier: GPL-2.0-only
config NVME_TARGET
tristate "NVMe Target support"
depends on BLOCK
select CONFIGFS_FS
select NVME_KEYRING if NVME_TARGET_TCP_TLS
select KEYS if NVME_TARGET_TCP_TLS
select SGL_ALLOC
help
This enabled target side support for the NVMe protocol, that is
it allows the Linux kernel to implement NVMe subsystems and
controllers and export Linux block devices as NVMe namespaces.
You need to select at least one of the transports below to make this
functionality useful.
To configure the NVMe target you probably want to use the nvmetcli
tool from http://git.infradead.org/users/hch/nvmetcli.git.
config NVME_TARGET_DEBUGFS
bool "NVMe Target debugfs support"
depends on NVME_TARGET
help
This enables debugfs support to display the connected controllers
to each subsystem
If unsure, say N.
config NVME_TARGET_PASSTHRU
bool "NVMe Target Passthrough support"
depends on NVME_TARGET
depends on NVME_CORE=y || NVME_CORE=NVME_TARGET
help
This enables target side NVMe passthru controller support for the
NVMe Over Fabrics protocol. It allows for hosts to manage and
directly access an actual NVMe controller residing on the target
side, including executing Vendor Unique Commands.
If unsure, say N.
config NVME_TARGET_LOOP
tristate "NVMe loopback device support"
depends on NVME_TARGET
select NVME_FABRICS
select SG_POOL
help
This enables the NVMe loopback device support, which can be useful
to test NVMe host and target side features.
If unsure, say N.
config NVME_TARGET_RDMA
tristate "NVMe over Fabrics RDMA target support"
depends on INFINIBAND && INFINIBAND_ADDR_TRANS
depends on NVME_TARGET
select SGL_ALLOC
help
This enables the NVMe RDMA target support, which allows exporting NVMe
devices over RDMA.
If unsure, say N.
config NVME_TARGET_FC
tristate "NVMe over Fabrics FC target driver"
depends on NVME_TARGET
depends on HAS_DMA
select SGL_ALLOC
help
This enables the NVMe FC target support, which allows exporting NVMe
devices over FC.
If unsure, say N.
config NVME_TARGET_FCLOOP
tristate "NVMe over Fabrics FC Transport Loopback Test driver"
depends on NVME_TARGET
select NVME_FABRICS
select SG_POOL
depends on NVME_FC
depends on NVME_TARGET_FC
help
This enables the NVMe FC loopback test support, which can be useful
to test NVMe-FC transport interfaces.
If unsure, say N.
config NVME_TARGET_TCP
tristate "NVMe over Fabrics TCP target support"
depends on INET
depends on NVME_TARGET
help
This enables the NVMe TCP target support, which allows exporting NVMe
devices over TCP.
If unsure, say N.
config NVME_TARGET_TCP_TLS
bool "NVMe over Fabrics TCP target TLS encryption support"
depends on NVME_TARGET_TCP
select NET_HANDSHAKE
select TLS
help
Enables TLS encryption for the NVMe TCP target using the netlink handshake API.
The TLS handshake daemon is available at
https://github.com/oracle/ktls-utils.
If unsure, say N.
config NVME_TARGET_AUTH
bool "NVMe over Fabrics In-band Authentication in target side"
depends on NVME_TARGET
select NVME_AUTH
help
This enables support for NVMe over Fabrics In-band Authentication in
target side.
If unsure, say N.
config NVME_TARGET_AUTH_DEBUG
bool "NVMe over Fabrics In-band Authentication debug messages"
depends on NVME_TARGET_AUTH
help
This enables additional debug messages including the generated
DH-HMAC-CHAP secrets to help debugging authentication failures.
If unsure, say N.
config NVME_TARGET_PCI_EPF
tristate "NVMe PCI Endpoint Function target support"
depends on NVME_TARGET && PCI_ENDPOINT
depends on NVME_CORE=y || NVME_CORE=NVME_TARGET
help
This enables the NVMe PCI Endpoint Function target driver support,
which allows creating a NVMe PCI controller using an endpoint mode
capable PCI controller.
If unsure, say N.
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0
ccflags-y += -I$(src)
obj-$(CONFIG_NVME_TARGET) += nvmet.o
obj-$(CONFIG_NVME_TARGET_LOOP) += nvme-loop.o
obj-$(CONFIG_NVME_TARGET_RDMA) += nvmet-rdma.o
obj-$(CONFIG_NVME_TARGET_FC) += nvmet-fc.o
obj-$(CONFIG_NVME_TARGET_FCLOOP) += nvme-fcloop.o
obj-$(CONFIG_NVME_TARGET_TCP) += nvmet-tcp.o
obj-$(CONFIG_NVME_TARGET_PCI_EPF) += nvmet-pci-epf.o
nvmet-y += core.o configfs.o admin-cmd.o fabrics-cmd.o \
discovery.o io-cmd-file.o io-cmd-bdev.o pr.o
nvmet-$(CONFIG_NVME_TARGET_DEBUGFS) += debugfs.o
nvmet-$(CONFIG_NVME_TARGET_PASSTHRU) += passthru.o
nvmet-$(CONFIG_BLK_DEV_ZONED) += zns.o
nvmet-$(CONFIG_NVME_TARGET_AUTH) += fabrics-cmd-auth.o auth.o
nvme-loop-y += loop.o
nvmet-rdma-y += rdma.o
nvmet-fc-y += fc.o
nvme-fcloop-y += fcloop.o
nvmet-tcp-y += tcp.o
nvmet-pci-epf-y += pci-epf.o
nvmet-$(CONFIG_TRACING) += trace.o
File diff suppressed because it is too large Load Diff
+527
View File
@@ -0,0 +1,527 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe over Fabrics DH-HMAC-CHAP authentication.
* Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.
* All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/crc32.h>
#include <linux/base64.h>
#include <linux/ctype.h>
#include <linux/random.h>
#include <linux/nvme-auth.h>
#include <linux/nvme-keyring.h>
#include <linux/unaligned.h>
#include "nvmet.h"
int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
bool set_ctrl)
{
unsigned char key_hash;
char *dhchap_secret;
if (!strlen(secret)) {
if (set_ctrl) {
kfree(host->dhchap_ctrl_secret);
host->dhchap_ctrl_secret = NULL;
host->dhchap_ctrl_key_hash = 0;
} else {
kfree(host->dhchap_secret);
host->dhchap_secret = NULL;
host->dhchap_key_hash = 0;
}
return 0;
}
if (sscanf(secret, "DHHC-1:%hhd:%*s", &key_hash) != 1)
return -EINVAL;
if (key_hash > 3) {
pr_warn("Invalid DH-HMAC-CHAP hash id %d\n",
key_hash);
return -EINVAL;
}
dhchap_secret = kstrdup(secret, GFP_KERNEL);
if (!dhchap_secret)
return -ENOMEM;
down_write(&nvmet_config_sem);
if (set_ctrl) {
kfree(host->dhchap_ctrl_secret);
host->dhchap_ctrl_secret = strim(dhchap_secret);
host->dhchap_ctrl_key_hash = key_hash;
} else {
kfree(host->dhchap_secret);
host->dhchap_secret = strim(dhchap_secret);
host->dhchap_key_hash = key_hash;
}
up_write(&nvmet_config_sem);
return 0;
}
int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id)
{
const char *dhgroup_kpp;
int ret = 0;
pr_debug("%s: ctrl %d selecting dhgroup %d\n",
__func__, ctrl->cntlid, dhgroup_id);
if (ctrl->dh_tfm) {
if (ctrl->dh_gid == dhgroup_id) {
pr_debug("%s: ctrl %d reuse existing DH group %d\n",
__func__, ctrl->cntlid, dhgroup_id);
return 0;
}
crypto_free_kpp(ctrl->dh_tfm);
ctrl->dh_tfm = NULL;
ctrl->dh_gid = 0;
}
if (dhgroup_id == NVME_AUTH_DHGROUP_NULL)
return 0;
dhgroup_kpp = nvme_auth_dhgroup_kpp(dhgroup_id);
if (!dhgroup_kpp) {
pr_debug("%s: ctrl %d invalid DH group %d\n",
__func__, ctrl->cntlid, dhgroup_id);
return -EINVAL;
}
ctrl->dh_tfm = crypto_alloc_kpp(dhgroup_kpp, 0, 0);
if (IS_ERR(ctrl->dh_tfm)) {
pr_debug("%s: ctrl %d failed to setup DH group %d, err %ld\n",
__func__, ctrl->cntlid, dhgroup_id,
PTR_ERR(ctrl->dh_tfm));
ret = PTR_ERR(ctrl->dh_tfm);
ctrl->dh_tfm = NULL;
ctrl->dh_gid = 0;
} else {
ctrl->dh_gid = dhgroup_id;
pr_debug("%s: ctrl %d setup DH group %d\n",
__func__, ctrl->cntlid, ctrl->dh_gid);
ret = nvme_auth_gen_privkey(ctrl->dh_tfm, ctrl->dh_gid);
if (ret < 0) {
pr_debug("%s: ctrl %d failed to generate private key, err %d\n",
__func__, ctrl->cntlid, ret);
kfree_sensitive(ctrl->dh_key);
ctrl->dh_key = NULL;
return ret;
}
ctrl->dh_keysize = crypto_kpp_maxsize(ctrl->dh_tfm);
kfree_sensitive(ctrl->dh_key);
ctrl->dh_key = kzalloc(ctrl->dh_keysize, GFP_KERNEL);
if (!ctrl->dh_key) {
pr_warn("ctrl %d failed to allocate public key\n",
ctrl->cntlid);
return -ENOMEM;
}
ret = nvme_auth_gen_pubkey(ctrl->dh_tfm, ctrl->dh_key,
ctrl->dh_keysize);
if (ret < 0) {
pr_warn("ctrl %d failed to generate public key\n",
ctrl->cntlid);
kfree(ctrl->dh_key);
ctrl->dh_key = NULL;
}
}
return ret;
}
u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq, bool reset)
{
int ret = 0;
struct nvmet_host_link *p;
struct nvmet_host *host = NULL;
down_read(&nvmet_config_sem);
if (nvmet_is_disc_subsys(ctrl->subsys))
goto out_unlock;
if (ctrl->subsys->allow_any_host)
goto out_unlock;
list_for_each_entry(p, &ctrl->subsys->hosts, entry) {
if (strcmp(nvmet_host_name(p->host), ctrl->hostnqn))
continue;
host = p->host;
break;
}
if (!host) {
pr_debug("host %s not found\n", ctrl->hostnqn);
ret = NVME_AUTH_DHCHAP_FAILURE_FAILED;
goto out_unlock;
}
if (!reset && nvmet_queue_tls_keyid(sq)) {
pr_debug("host %s tls enabled\n", ctrl->hostnqn);
goto out_unlock;
}
ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id);
if (ret < 0) {
pr_warn("Failed to setup DH group");
ret = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
goto out_unlock;
}
if (!host->dhchap_secret) {
pr_debug("No authentication provided\n");
goto out_unlock;
}
if (host->dhchap_hash_id == ctrl->shash_id) {
pr_debug("Re-use existing hash ID %d\n",
ctrl->shash_id);
} else {
ctrl->shash_id = host->dhchap_hash_id;
}
/* Skip the 'DHHC-1:XX:' prefix */
nvme_auth_free_key(ctrl->host_key);
ctrl->host_key = nvme_auth_extract_key(host->dhchap_secret + 10,
host->dhchap_key_hash);
if (IS_ERR(ctrl->host_key)) {
ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE;
ctrl->host_key = NULL;
goto out_free_hash;
}
#ifdef CONFIG_NVME_TARGET_AUTH_DEBUG
pr_debug("%s: using hash %s key %*ph\n", __func__,
ctrl->host_key->hash > 0 ?
nvme_auth_hmac_name(ctrl->host_key->hash) : "none",
(int)ctrl->host_key->len, ctrl->host_key->key);
#endif
nvme_auth_free_key(ctrl->ctrl_key);
if (!host->dhchap_ctrl_secret) {
ctrl->ctrl_key = NULL;
goto out_unlock;
}
ctrl->ctrl_key = nvme_auth_extract_key(host->dhchap_ctrl_secret + 10,
host->dhchap_ctrl_key_hash);
if (IS_ERR(ctrl->ctrl_key)) {
ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE;
ctrl->ctrl_key = NULL;
goto out_free_hash;
}
#ifdef CONFIG_NVME_TARGET_AUTH_DEBUG
pr_debug("%s: using ctrl hash %s key %*ph\n", __func__,
ctrl->ctrl_key->hash > 0 ?
nvme_auth_hmac_name(ctrl->ctrl_key->hash) : "none",
(int)ctrl->ctrl_key->len, ctrl->ctrl_key->key);
#endif
out_free_hash:
if (ret) {
if (ctrl->host_key) {
nvme_auth_free_key(ctrl->host_key);
ctrl->host_key = NULL;
}
ctrl->shash_id = 0;
}
out_unlock:
up_read(&nvmet_config_sem);
return ret;
}
void nvmet_auth_sq_free(struct nvmet_sq *sq)
{
cancel_delayed_work(&sq->auth_expired_work);
kfree(sq->dhchap_c1);
sq->dhchap_c1 = NULL;
kfree(sq->dhchap_c2);
sq->dhchap_c2 = NULL;
kfree(sq->dhchap_skey);
sq->dhchap_skey = NULL;
}
void nvmet_destroy_auth(struct nvmet_ctrl *ctrl)
{
ctrl->shash_id = 0;
if (ctrl->dh_tfm) {
crypto_free_kpp(ctrl->dh_tfm);
ctrl->dh_tfm = NULL;
ctrl->dh_gid = 0;
}
kfree_sensitive(ctrl->dh_key);
ctrl->dh_key = NULL;
if (ctrl->host_key) {
nvme_auth_free_key(ctrl->host_key);
ctrl->host_key = NULL;
}
if (ctrl->ctrl_key) {
nvme_auth_free_key(ctrl->ctrl_key);
ctrl->ctrl_key = NULL;
}
#ifdef CONFIG_NVME_TARGET_TCP_TLS
if (ctrl->tls_key) {
key_put(ctrl->tls_key);
ctrl->tls_key = NULL;
}
#endif
}
bool nvmet_check_auth_status(struct nvmet_req *req)
{
if (req->sq->ctrl->host_key) {
if (req->sq->qid > 0)
return true;
if (!req->sq->authenticated)
return false;
}
return true;
}
int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
unsigned int shash_len)
{
struct nvme_auth_hmac_ctx hmac;
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u8 *challenge = req->sq->dhchap_c1;
struct nvme_dhchap_key *transformed_key;
u8 buf[4];
int ret;
transformed_key = nvme_auth_transform_key(ctrl->host_key,
ctrl->hostnqn);
if (IS_ERR(transformed_key))
return PTR_ERR(transformed_key);
ret = nvme_auth_hmac_init(&hmac, ctrl->shash_id, transformed_key->key,
transformed_key->len);
if (ret)
goto out_free_response;
if (shash_len != nvme_auth_hmac_hash_len(ctrl->shash_id)) {
pr_err("%s: hash len mismatch (len %u digest %zu)\n", __func__,
shash_len, nvme_auth_hmac_hash_len(ctrl->shash_id));
ret = -EINVAL;
goto out_free_response;
}
if (ctrl->dh_gid != NVME_AUTH_DHGROUP_NULL) {
challenge = kmalloc(shash_len, GFP_KERNEL);
if (!challenge) {
ret = -ENOMEM;
goto out_free_response;
}
ret = nvme_auth_augmented_challenge(ctrl->shash_id,
req->sq->dhchap_skey,
req->sq->dhchap_skey_len,
req->sq->dhchap_c1,
challenge, shash_len);
if (ret)
goto out_free_challenge;
}
pr_debug("ctrl %d qid %d host response seq %u transaction %d\n",
ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,
req->sq->dhchap_tid);
nvme_auth_hmac_update(&hmac, challenge, shash_len);
put_unaligned_le32(req->sq->dhchap_s1, buf);
nvme_auth_hmac_update(&hmac, buf, 4);
put_unaligned_le16(req->sq->dhchap_tid, buf);
nvme_auth_hmac_update(&hmac, buf, 2);
*buf = req->sq->sc_c;
nvme_auth_hmac_update(&hmac, buf, 1);
nvme_auth_hmac_update(&hmac, "HostHost", 8);
memset(buf, 0, 4);
nvme_auth_hmac_update(&hmac, ctrl->hostnqn, strlen(ctrl->hostnqn));
nvme_auth_hmac_update(&hmac, buf, 1);
nvme_auth_hmac_update(&hmac, ctrl->subsys->subsysnqn,
strlen(ctrl->subsys->subsysnqn));
nvme_auth_hmac_final(&hmac, response);
ret = 0;
out_free_challenge:
if (challenge != req->sq->dhchap_c1)
kfree(challenge);
out_free_response:
memzero_explicit(&hmac, sizeof(hmac));
nvme_auth_free_key(transformed_key);
return ret;
}
int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
unsigned int shash_len)
{
struct nvme_auth_hmac_ctx hmac;
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u8 *challenge = req->sq->dhchap_c2;
struct nvme_dhchap_key *transformed_key;
u8 buf[4];
int ret;
transformed_key = nvme_auth_transform_key(ctrl->ctrl_key,
ctrl->subsys->subsysnqn);
if (IS_ERR(transformed_key))
return PTR_ERR(transformed_key);
ret = nvme_auth_hmac_init(&hmac, ctrl->shash_id, transformed_key->key,
transformed_key->len);
if (ret)
goto out_free_response;
if (shash_len != nvme_auth_hmac_hash_len(ctrl->shash_id)) {
pr_err("%s: hash len mismatch (len %u digest %zu)\n", __func__,
shash_len, nvme_auth_hmac_hash_len(ctrl->shash_id));
ret = -EINVAL;
goto out_free_response;
}
if (ctrl->dh_gid != NVME_AUTH_DHGROUP_NULL) {
challenge = kmalloc(shash_len, GFP_KERNEL);
if (!challenge) {
ret = -ENOMEM;
goto out_free_response;
}
ret = nvme_auth_augmented_challenge(ctrl->shash_id,
req->sq->dhchap_skey,
req->sq->dhchap_skey_len,
req->sq->dhchap_c2,
challenge, shash_len);
if (ret)
goto out_free_challenge;
}
nvme_auth_hmac_update(&hmac, challenge, shash_len);
put_unaligned_le32(req->sq->dhchap_s2, buf);
nvme_auth_hmac_update(&hmac, buf, 4);
put_unaligned_le16(req->sq->dhchap_tid, buf);
nvme_auth_hmac_update(&hmac, buf, 2);
*buf = req->sq->sc_c;
nvme_auth_hmac_update(&hmac, buf, 1);
nvme_auth_hmac_update(&hmac, "Controller", 10);
nvme_auth_hmac_update(&hmac, ctrl->subsys->subsysnqn,
strlen(ctrl->subsys->subsysnqn));
memset(buf, 0, 4);
nvme_auth_hmac_update(&hmac, buf, 1);
nvme_auth_hmac_update(&hmac, ctrl->hostnqn, strlen(ctrl->hostnqn));
nvme_auth_hmac_final(&hmac, response);
ret = 0;
out_free_challenge:
if (challenge != req->sq->dhchap_c2)
kfree(challenge);
out_free_response:
memzero_explicit(&hmac, sizeof(hmac));
nvme_auth_free_key(transformed_key);
return ret;
}
int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
u8 *buf, int buf_size)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int ret = 0;
if (!ctrl->dh_key) {
pr_warn("ctrl %d no DH public key!\n", ctrl->cntlid);
return -ENOKEY;
}
if (buf_size != ctrl->dh_keysize) {
pr_warn("ctrl %d DH public key size mismatch, need %zu is %d\n",
ctrl->cntlid, ctrl->dh_keysize, buf_size);
ret = -EINVAL;
} else {
memcpy(buf, ctrl->dh_key, buf_size);
#ifdef CONFIG_NVME_TARGET_AUTH_DEBUG
pr_debug("%s: ctrl %d public key %*ph\n", __func__,
ctrl->cntlid, (int)buf_size, buf);
#endif
}
return ret;
}
int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
const u8 *pkey, int pkey_size)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int ret;
req->sq->dhchap_skey_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);
if (!req->sq->dhchap_skey)
return -ENOMEM;
ret = nvme_auth_gen_session_key(ctrl->dh_tfm,
pkey, pkey_size,
req->sq->dhchap_skey,
req->sq->dhchap_skey_len,
ctrl->shash_id);
if (ret)
pr_debug("failed to compute session key, err %d\n", ret);
#ifdef CONFIG_NVME_TARGET_AUTH_DEBUG
else
pr_debug("%s: session key %*ph\n", __func__,
(int)req->sq->dhchap_skey_len,
req->sq->dhchap_skey);
#endif
return ret;
}
void nvmet_auth_insert_psk(struct nvmet_sq *sq)
{
int hash_len = nvme_auth_hmac_hash_len(sq->ctrl->shash_id);
u8 *psk, *tls_psk;
char *digest;
size_t psk_len;
int ret;
#ifdef CONFIG_NVME_TARGET_TCP_TLS
struct key *tls_key = NULL;
#endif
ret = nvme_auth_generate_psk(sq->ctrl->shash_id,
sq->dhchap_skey,
sq->dhchap_skey_len,
sq->dhchap_c1, sq->dhchap_c2,
hash_len, &psk, &psk_len);
if (ret) {
pr_warn("%s: ctrl %d qid %d failed to generate PSK, error %d\n",
__func__, sq->ctrl->cntlid, sq->qid, ret);
return;
}
ret = nvme_auth_generate_digest(sq->ctrl->shash_id, psk, psk_len,
sq->ctrl->subsys->subsysnqn,
sq->ctrl->hostnqn, &digest);
if (ret) {
pr_warn("%s: ctrl %d qid %d failed to generate digest, error %d\n",
__func__, sq->ctrl->cntlid, sq->qid, ret);
goto out_free_psk;
}
ret = nvme_auth_derive_tls_psk(sq->ctrl->shash_id, psk, psk_len,
digest, &tls_psk);
if (ret) {
pr_warn("%s: ctrl %d qid %d failed to derive TLS PSK, error %d\n",
__func__, sq->ctrl->cntlid, sq->qid, ret);
goto out_free_digest;
}
#ifdef CONFIG_NVME_TARGET_TCP_TLS
tls_key = nvme_tls_psk_refresh(NULL, sq->ctrl->hostnqn,
sq->ctrl->subsys->subsysnqn,
sq->ctrl->shash_id, tls_psk, psk_len,
digest);
if (IS_ERR(tls_key)) {
pr_warn("%s: ctrl %d qid %d failed to refresh key, error %ld\n",
__func__, sq->ctrl->cntlid, sq->qid, PTR_ERR(tls_key));
tls_key = NULL;
}
if (sq->ctrl->tls_key)
key_put(sq->ctrl->tls_key);
sq->ctrl->tls_key = tls_key;
#endif
kfree_sensitive(tls_psk);
out_free_digest:
kfree_sensitive(digest);
out_free_psk:
kfree_sensitive(psk);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+229
View File
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DebugFS interface for the NVMe target.
* Copyright (c) 2022-2024 Shadow
* Copyright (c) 2024 SUSE LLC
*/
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include "nvmet.h"
#include "debugfs.h"
static struct dentry *nvmet_debugfs;
#define NVMET_DEBUGFS_ATTR(field) \
static int field##_open(struct inode *inode, struct file *file) \
{ return single_open(file, field##_show, inode->i_private); } \
\
static const struct file_operations field##_fops = { \
.open = field##_open, \
.read = seq_read, \
.release = single_release, \
}
#define NVMET_DEBUGFS_RW_ATTR(field) \
static int field##_open(struct inode *inode, struct file *file) \
{ return single_open(file, field##_show, inode->i_private); } \
\
static const struct file_operations field##_fops = { \
.open = field##_open, \
.read = seq_read, \
.write = field##_write, \
.release = single_release, \
}
static int nvmet_ctrl_hostnqn_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
seq_puts(m, ctrl->hostnqn);
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_hostnqn);
static int nvmet_ctrl_kato_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
seq_printf(m, "%d\n", ctrl->kato);
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_kato);
static int nvmet_ctrl_port_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
seq_printf(m, "%d\n", le16_to_cpu(ctrl->port->disc_addr.portid));
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_port);
static const char *const csts_state_names[] = {
[NVME_CSTS_RDY] = "ready",
[NVME_CSTS_CFS] = "fatal",
[NVME_CSTS_NSSRO] = "reset",
[NVME_CSTS_SHST_OCCUR] = "shutdown",
[NVME_CSTS_SHST_CMPLT] = "completed",
[NVME_CSTS_PP] = "paused",
};
static int nvmet_ctrl_state_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
bool sep = false;
int i;
for (i = 0; i < ARRAY_SIZE(csts_state_names); i++) {
int state = BIT(i);
if (!(ctrl->csts & state))
continue;
if (sep)
seq_puts(m, "|");
sep = true;
if (csts_state_names[state])
seq_puts(m, csts_state_names[state]);
else
seq_printf(m, "%d", state);
}
if (sep)
seq_printf(m, "\n");
return 0;
}
static ssize_t nvmet_ctrl_state_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct seq_file *m = file->private_data;
struct nvmet_ctrl *ctrl = m->private;
char reset[16];
if (count >= sizeof(reset))
return -EINVAL;
if (copy_from_user(reset, buf, count))
return -EFAULT;
if (!memcmp(reset, "fatal", 5))
nvmet_ctrl_fatal_error(ctrl);
else
return -EINVAL;
return count;
}
NVMET_DEBUGFS_RW_ATTR(nvmet_ctrl_state);
static int nvmet_ctrl_host_traddr_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
ssize_t size;
char buf[NVMF_TRADDR_SIZE + 1];
size = nvmet_ctrl_host_traddr(ctrl, buf, NVMF_TRADDR_SIZE);
if (size < 0) {
buf[0] = '\0';
size = 0;
}
buf[size] = '\0';
seq_printf(m, "%s\n", buf);
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_host_traddr);
#ifdef CONFIG_NVME_TARGET_TCP_TLS
static int nvmet_ctrl_tls_key_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
key_serial_t keyid = nvmet_queue_tls_keyid(ctrl->sqs[0]);
seq_printf(m, "%08x\n", keyid);
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_key);
static int nvmet_ctrl_tls_concat_show(struct seq_file *m, void *p)
{
struct nvmet_ctrl *ctrl = m->private;
seq_printf(m, "%d\n", ctrl->concat);
return 0;
}
NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_concat);
#endif
int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl)
{
char name[32];
struct dentry *parent = ctrl->subsys->debugfs_dir;
int ret;
if (!parent)
return -ENODEV;
snprintf(name, sizeof(name), "ctrl%d", ctrl->cntlid);
ctrl->debugfs_dir = debugfs_create_dir(name, parent);
if (IS_ERR(ctrl->debugfs_dir)) {
ret = PTR_ERR(ctrl->debugfs_dir);
ctrl->debugfs_dir = NULL;
return ret;
}
debugfs_create_file("port", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_port_fops);
debugfs_create_file("hostnqn", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_hostnqn_fops);
debugfs_create_file("kato", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_kato_fops);
debugfs_create_file("state", S_IRUSR | S_IWUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_state_fops);
debugfs_create_file("host_traddr", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_host_traddr_fops);
#ifdef CONFIG_NVME_TARGET_TCP_TLS
debugfs_create_file("tls_concat", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_tls_concat_fops);
debugfs_create_file("tls_key", S_IRUSR, ctrl->debugfs_dir, ctrl,
&nvmet_ctrl_tls_key_fops);
#endif
return 0;
}
void nvmet_debugfs_ctrl_free(struct nvmet_ctrl *ctrl)
{
debugfs_remove_recursive(ctrl->debugfs_dir);
}
int nvmet_debugfs_subsys_setup(struct nvmet_subsys *subsys)
{
int ret = 0;
subsys->debugfs_dir = debugfs_create_dir(subsys->subsysnqn,
nvmet_debugfs);
if (IS_ERR(subsys->debugfs_dir)) {
ret = PTR_ERR(subsys->debugfs_dir);
subsys->debugfs_dir = NULL;
}
return ret;
}
void nvmet_debugfs_subsys_free(struct nvmet_subsys *subsys)
{
debugfs_remove_recursive(subsys->debugfs_dir);
}
int __init nvmet_init_debugfs(void)
{
struct dentry *parent;
parent = debugfs_create_dir("nvmet", NULL);
if (IS_ERR(parent)) {
pr_warn("%s: failed to create debugfs directory\n", "nvmet");
return PTR_ERR(parent);
}
nvmet_debugfs = parent;
return 0;
}
void nvmet_exit_debugfs(void)
{
debugfs_remove_recursive(nvmet_debugfs);
}
+42
View File
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* DebugFS interface for the NVMe target.
* Copyright (c) 2022-2024 Shadow
* Copyright (c) 2024 SUSE LLC
*/
#ifndef NVMET_DEBUGFS_H
#define NVMET_DEBUGFS_H
#include <linux/types.h>
#ifdef CONFIG_NVME_TARGET_DEBUGFS
int nvmet_debugfs_subsys_setup(struct nvmet_subsys *subsys);
void nvmet_debugfs_subsys_free(struct nvmet_subsys *subsys);
int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl);
void nvmet_debugfs_ctrl_free(struct nvmet_ctrl *ctrl);
int __init nvmet_init_debugfs(void);
void nvmet_exit_debugfs(void);
#else
static inline int nvmet_debugfs_subsys_setup(struct nvmet_subsys *subsys)
{
return 0;
}
static inline void nvmet_debugfs_subsys_free(struct nvmet_subsys *subsys){}
static inline int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl)
{
return 0;
}
static inline void nvmet_debugfs_ctrl_free(struct nvmet_ctrl *ctrl) {}
static inline int __init nvmet_init_debugfs(void)
{
return 0;
}
static inline void nvmet_exit_debugfs(void) {}
#endif
#endif /* NVMET_DEBUGFS_H */
+442
View File
@@ -0,0 +1,442 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Discovery service for the NVMe over Fabrics target.
* Copyright (C) 2016 Intel Corporation. All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/slab.h>
#include <generated/utsrelease.h>
#include "nvmet.h"
struct nvmet_subsys *nvmet_disc_subsys;
static u64 nvmet_genctr;
static void __nvmet_disc_changed(struct nvmet_port *port,
struct nvmet_ctrl *ctrl)
{
if (ctrl->port != port)
return;
if (nvmet_aen_bit_disabled(ctrl, NVME_AEN_BIT_DISC_CHANGE))
return;
nvmet_add_async_event(ctrl, NVME_AER_NOTICE,
NVME_AER_NOTICE_DISC_CHANGED, NVME_LOG_DISC);
}
void nvmet_port_disc_changed(struct nvmet_port *port,
struct nvmet_subsys *subsys)
{
struct nvmet_ctrl *ctrl;
lockdep_assert_held(&nvmet_config_sem);
nvmet_genctr++;
mutex_lock(&nvmet_disc_subsys->lock);
list_for_each_entry(ctrl, &nvmet_disc_subsys->ctrls, subsys_entry) {
if (subsys && !nvmet_host_allowed(subsys, ctrl->hostnqn))
continue;
__nvmet_disc_changed(port, ctrl);
}
mutex_unlock(&nvmet_disc_subsys->lock);
/* If transport can signal change, notify transport */
if (port->tr_ops && port->tr_ops->discovery_chg)
port->tr_ops->discovery_chg(port);
}
static void __nvmet_subsys_disc_changed(struct nvmet_port *port,
struct nvmet_subsys *subsys,
struct nvmet_host *host)
{
struct nvmet_ctrl *ctrl;
mutex_lock(&nvmet_disc_subsys->lock);
list_for_each_entry(ctrl, &nvmet_disc_subsys->ctrls, subsys_entry) {
if (host && strcmp(nvmet_host_name(host), ctrl->hostnqn))
continue;
__nvmet_disc_changed(port, ctrl);
}
mutex_unlock(&nvmet_disc_subsys->lock);
}
void nvmet_subsys_disc_changed(struct nvmet_subsys *subsys,
struct nvmet_host *host)
{
struct nvmet_port *port;
struct nvmet_subsys_link *s;
lockdep_assert_held(&nvmet_config_sem);
nvmet_genctr++;
list_for_each_entry(port, nvmet_ports, global_entry)
list_for_each_entry(s, &port->subsystems, entry) {
if (s->subsys != subsys)
continue;
__nvmet_subsys_disc_changed(port, subsys, host);
}
}
void nvmet_referral_enable(struct nvmet_port *parent, struct nvmet_port *port)
{
down_write(&nvmet_config_sem);
if (list_empty(&port->entry)) {
list_add_tail(&port->entry, &parent->referrals);
port->enabled = true;
nvmet_port_disc_changed(parent, NULL);
}
up_write(&nvmet_config_sem);
}
void nvmet_referral_disable(struct nvmet_port *parent, struct nvmet_port *port)
{
down_write(&nvmet_config_sem);
if (!list_empty(&port->entry)) {
port->enabled = false;
list_del_init(&port->entry);
nvmet_port_disc_changed(parent, NULL);
}
up_write(&nvmet_config_sem);
}
static void nvmet_format_discovery_entry(struct nvmf_disc_rsp_page_hdr *hdr,
struct nvmet_port *port, char *subsys_nqn, char *traddr,
u8 type, u32 numrec)
{
struct nvmf_disc_rsp_page_entry *e = &hdr->entries[numrec];
e->trtype = port->disc_addr.trtype;
e->adrfam = port->disc_addr.adrfam;
e->treq = port->disc_addr.treq;
e->portid = port->disc_addr.portid;
/* we support only dynamic controllers */
e->cntlid = cpu_to_le16(NVME_CNTLID_DYNAMIC);
e->asqsz = cpu_to_le16(NVME_AQ_DEPTH);
e->subtype = type;
memcpy(e->trsvcid, port->disc_addr.trsvcid, NVMF_TRSVCID_SIZE);
memcpy(e->traddr, traddr, NVMF_TRADDR_SIZE);
memcpy(e->tsas.common, port->disc_addr.tsas.common, NVMF_TSAS_SIZE);
strscpy(e->subnqn, subsys_nqn, NVMF_NQN_SIZE);
}
/*
* nvmet_set_disc_traddr - set a correct discovery log entry traddr
*
* IP based transports (e.g RDMA) can listen on "any" ipv4/ipv6 addresses
* (INADDR_ANY or IN6ADDR_ANY_INIT). The discovery log page traddr reply
* must not contain that "any" IP address. If the transport implements
* .disc_traddr, use it. this callback will set the discovery traddr
* from the req->port address in case the port in question listens
* "any" IP address.
*/
static void nvmet_set_disc_traddr(struct nvmet_req *req, struct nvmet_port *port,
char *traddr)
{
if (req->ops->disc_traddr)
req->ops->disc_traddr(req, port, traddr);
else
memcpy(traddr, port->disc_addr.traddr, NVMF_TRADDR_SIZE);
}
static size_t discovery_log_entries(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmet_subsys_link *p;
struct nvmet_port *r;
size_t entries = 1;
list_for_each_entry(p, &req->port->subsystems, entry) {
if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn))
continue;
entries++;
}
list_for_each_entry(r, &req->port->referrals, entry)
entries++;
return entries;
}
static void nvmet_execute_disc_get_log_page(struct nvmet_req *req)
{
const int entry_size = sizeof(struct nvmf_disc_rsp_page_entry);
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmf_disc_rsp_page_hdr *hdr;
u64 offset = nvmet_get_log_page_offset(req->cmd);
size_t data_len = nvmet_get_log_page_len(req->cmd);
size_t alloc_len;
size_t copy_len;
struct nvmet_subsys_link *p;
struct nvmet_port *r;
u32 numrec = 0;
u16 status = 0;
void *buffer;
char traddr[NVMF_TRADDR_SIZE];
if (!nvmet_check_transfer_len(req, data_len))
return;
if (req->cmd->get_log_page.lid != NVME_LOG_DISC) {
req->error_loc =
offsetof(struct nvme_get_log_page_command, lid);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
/* Spec requires dword aligned offsets */
if (offset & 0x3) {
req->error_loc =
offsetof(struct nvme_get_log_page_command, lpo);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
/*
* Make sure we're passing at least a buffer of response header size.
* If host provided data len is less than the header size, only the
* number of bytes requested by host will be sent to host.
*/
down_read(&nvmet_config_sem);
alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req);
buffer = kzalloc(alloc_len, GFP_KERNEL);
if (!buffer) {
up_read(&nvmet_config_sem);
status = NVME_SC_INTERNAL;
goto out;
}
hdr = buffer;
nvmet_set_disc_traddr(req, req->port, traddr);
nvmet_format_discovery_entry(hdr, req->port,
nvmet_disc_subsys->subsysnqn,
traddr, NVME_NQN_CURR, numrec);
numrec++;
list_for_each_entry(p, &req->port->subsystems, entry) {
if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn))
continue;
nvmet_format_discovery_entry(hdr, req->port,
p->subsys->subsysnqn, traddr,
NVME_NQN_NVME, numrec);
numrec++;
}
list_for_each_entry(r, &req->port->referrals, entry) {
if (r->disc_addr.trtype == NVMF_TRTYPE_PCI)
continue;
nvmet_format_discovery_entry(hdr, r,
NVME_DISC_SUBSYS_NAME,
r->disc_addr.traddr,
NVME_NQN_DISC, numrec);
numrec++;
}
hdr->genctr = cpu_to_le64(nvmet_genctr);
hdr->numrec = cpu_to_le64(numrec);
hdr->recfmt = cpu_to_le16(0);
nvmet_clear_aen_bit(req, NVME_AEN_BIT_DISC_CHANGE);
up_read(&nvmet_config_sem);
/*
* Validate the host-supplied log page offset before copying out.
* Without this check, the host controls a 64-bit byte offset into
* a small kzalloc'd buffer: a value past the log page lets the
* subsequent memcpy read adjacent kernel heap, and a value aimed
* at unmapped kernel memory faults the in-kernel copy and crashes
* the target host. The Discovery controller is unauthenticated,
* so the bug is reachable from any reachable fabric peer.
*/
if (offset > alloc_len) {
req->error_loc =
offsetof(struct nvme_get_log_page_command, lpo);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out_free_buffer;
}
copy_len = min_t(size_t, data_len, alloc_len - offset);
status = nvmet_copy_to_sgl(req, 0, buffer + offset, copy_len);
if (!status && copy_len < data_len)
status = nvmet_zero_sgl(req, copy_len, data_len - copy_len);
out_free_buffer:
kfree(buffer);
out:
nvmet_req_complete(req, status);
}
static void nvmet_execute_disc_identify(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvme_id_ctrl *id;
u16 status = 0;
if (!nvmet_check_transfer_len(req, NVME_IDENTIFY_DATA_SIZE))
return;
if (req->cmd->identify.cns != NVME_ID_CNS_CTRL) {
req->error_loc = offsetof(struct nvme_identify, cns);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
id = kzalloc_obj(*id);
if (!id) {
status = NVME_SC_INTERNAL;
goto out;
}
memcpy(id->sn, ctrl->subsys->serial, NVMET_SN_MAX_SIZE);
memset(id->fr, ' ', sizeof(id->fr));
memcpy_and_pad(id->mn, sizeof(id->mn), ctrl->subsys->model_number,
strlen(ctrl->subsys->model_number), ' ');
memcpy_and_pad(id->fr, sizeof(id->fr),
UTS_RELEASE, strlen(UTS_RELEASE), ' ');
id->cntrltype = NVME_CTRL_DISC;
/* no limit on data transfer sizes for now */
id->mdts = 0;
id->cntlid = cpu_to_le16(ctrl->cntlid);
id->ver = cpu_to_le32(ctrl->subsys->ver);
id->lpa = (1 << 2);
/* no enforcement soft-limit for maxcmd - pick arbitrary high value */
id->maxcmd = cpu_to_le16(NVMET_MAX_CMD(ctrl));
id->sgls = cpu_to_le32(1 << 0); /* we always support SGLs */
if (ctrl->ops->flags & NVMF_KEYED_SGLS)
id->sgls |= cpu_to_le32(1 << 2);
if (req->port->inline_data_size)
id->sgls |= cpu_to_le32(1 << 20);
id->oaes = cpu_to_le32(NVMET_DISC_AEN_CFG_OPTIONAL);
strscpy(id->subnqn, ctrl->subsys->subsysnqn, sizeof(id->subnqn));
status = nvmet_copy_to_sgl(req, 0, id, sizeof(*id));
kfree(id);
out:
nvmet_req_complete(req, status);
}
static void nvmet_execute_disc_set_features(struct nvmet_req *req)
{
u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10);
u16 stat;
if (!nvmet_check_transfer_len(req, 0))
return;
switch (cdw10 & 0xff) {
case NVME_FEAT_KATO:
stat = nvmet_set_feat_kato(req);
break;
case NVME_FEAT_ASYNC_EVENT:
stat = nvmet_set_feat_async_event(req,
NVMET_DISC_AEN_CFG_OPTIONAL);
break;
default:
req->error_loc =
offsetof(struct nvme_common_command, cdw10);
stat = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
break;
}
nvmet_req_complete(req, stat);
}
static void nvmet_execute_disc_get_features(struct nvmet_req *req)
{
u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10);
u16 stat = 0;
if (!nvmet_check_transfer_len(req, 0))
return;
switch (cdw10 & 0xff) {
case NVME_FEAT_KATO:
nvmet_get_feat_kato(req);
break;
case NVME_FEAT_ASYNC_EVENT:
nvmet_get_feat_async_event(req);
break;
default:
req->error_loc =
offsetof(struct nvme_common_command, cdw10);
stat = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
break;
}
nvmet_req_complete(req, stat);
}
u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->common.opcode) {
case nvme_admin_get_log_page:
return nvmet_get_log_page_len(req->cmd);
case nvme_admin_identify:
return NVME_IDENTIFY_DATA_SIZE;
default:
return 0;
}
}
u16 nvmet_parse_discovery_cmd(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
if (unlikely(!(req->sq->ctrl->csts & NVME_CSTS_RDY))) {
pr_err("got cmd %d while not ready\n",
cmd->common.opcode);
req->error_loc =
offsetof(struct nvme_common_command, opcode);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
switch (cmd->common.opcode) {
case nvme_admin_set_features:
req->execute = nvmet_execute_disc_set_features;
return 0;
case nvme_admin_get_features:
req->execute = nvmet_execute_disc_get_features;
return 0;
case nvme_admin_async_event:
req->execute = nvmet_execute_async_event;
return 0;
case nvme_admin_keep_alive:
req->execute = nvmet_execute_keep_alive;
return 0;
case nvme_admin_get_log_page:
req->execute = nvmet_execute_disc_get_log_page;
return 0;
case nvme_admin_identify:
req->execute = nvmet_execute_disc_identify;
return 0;
default:
pr_debug("unhandled cmd %d\n", cmd->common.opcode);
req->error_loc = offsetof(struct nvme_common_command, opcode);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
}
int __init nvmet_init_discovery(void)
{
nvmet_disc_subsys =
nvmet_subsys_alloc(NVME_DISC_SUBSYS_NAME, NVME_NQN_CURR);
return PTR_ERR_OR_ZERO(nvmet_disc_subsys);
}
void nvmet_exit_discovery(void)
{
nvmet_subsys_put(nvmet_disc_subsys);
}
@@ -0,0 +1,616 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe over Fabrics DH-HMAC-CHAP authentication command handling.
* Copyright (c) 2020 Hannes Reinecke, SUSE Software Solutions.
* All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/blkdev.h>
#include <linux/random.h>
#include <linux/nvme-auth.h>
#include <crypto/kpp.h>
#include "nvmet.h"
static void nvmet_auth_expired_work(struct work_struct *work)
{
struct nvmet_sq *sq = container_of(to_delayed_work(work),
struct nvmet_sq, auth_expired_work);
pr_debug("%s: ctrl %d qid %d transaction %u expired, resetting\n",
__func__, sq->ctrl->cntlid, sq->qid, sq->dhchap_tid);
sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
sq->dhchap_tid = -1;
}
void nvmet_auth_sq_init(struct nvmet_sq *sq)
{
/* Initialize in-band authentication */
INIT_DELAYED_WORK(&sq->auth_expired_work, nvmet_auth_expired_work);
sq->authenticated = false;
sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
}
static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmf_auth_dhchap_negotiate_data *data = d;
int i, hash_id = 0, fallback_hash_id = 0, dhgid, fallback_dhgid;
pr_debug("%s: ctrl %d qid %d: data sc_d %d napd %d authid %d halen %d dhlen %d\n",
__func__, ctrl->cntlid, req->sq->qid,
data->sc_c, data->napd, data->auth_protocol[0].dhchap.authid,
data->auth_protocol[0].dhchap.halen,
data->auth_protocol[0].dhchap.dhlen);
req->sq->dhchap_tid = le16_to_cpu(data->t_id);
req->sq->sc_c = data->sc_c;
if (data->sc_c != NVME_AUTH_SECP_NOSC) {
if (!IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS))
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
/* Secure concatenation can only be enabled on the admin queue */
if (req->sq->qid)
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
switch (data->sc_c) {
case NVME_AUTH_SECP_NEWTLSPSK:
if (nvmet_queue_tls_keyid(req->sq))
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
break;
case NVME_AUTH_SECP_REPLACETLSPSK:
if (!nvmet_queue_tls_keyid(req->sq))
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
break;
default:
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
}
ctrl->concat = true;
}
if (data->napd != 1)
return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
if (data->auth_protocol[0].dhchap.authid !=
NVME_AUTH_DHCHAP_AUTH_ID)
return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
for (i = 0; i < data->auth_protocol[0].dhchap.halen; i++) {
u8 host_hmac_id = data->auth_protocol[0].dhchap.idlist[i];
if (!fallback_hash_id && nvme_auth_hmac_hash_len(host_hmac_id))
fallback_hash_id = host_hmac_id;
if (ctrl->shash_id != host_hmac_id)
continue;
hash_id = ctrl->shash_id;
break;
}
if (hash_id == 0) {
if (fallback_hash_id == 0) {
pr_debug("%s: ctrl %d qid %d: no usable hash found\n",
__func__, ctrl->cntlid, req->sq->qid);
return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
}
pr_debug("%s: ctrl %d qid %d: no usable hash found, falling back to %s\n",
__func__, ctrl->cntlid, req->sq->qid,
nvme_auth_hmac_name(fallback_hash_id));
ctrl->shash_id = fallback_hash_id;
}
dhgid = -1;
fallback_dhgid = -1;
for (i = 0; i < data->auth_protocol[0].dhchap.dhlen; i++) {
int tmp_dhgid = data->auth_protocol[0].dhchap.idlist[i + 30];
if (tmp_dhgid != ctrl->dh_gid) {
dhgid = tmp_dhgid;
break;
}
if (fallback_dhgid < 0) {
const char *kpp = nvme_auth_dhgroup_kpp(tmp_dhgid);
if (crypto_has_kpp(kpp, 0, 0))
fallback_dhgid = tmp_dhgid;
}
}
if (dhgid < 0) {
if (fallback_dhgid < 0) {
pr_debug("%s: ctrl %d qid %d: no usable DH group found\n",
__func__, ctrl->cntlid, req->sq->qid);
return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
}
pr_debug("%s: ctrl %d qid %d: configured DH group %s not found\n",
__func__, ctrl->cntlid, req->sq->qid,
nvme_auth_dhgroup_name(fallback_dhgid));
ctrl->dh_gid = fallback_dhgid;
}
if (ctrl->dh_gid == NVME_AUTH_DHGROUP_NULL && ctrl->concat) {
pr_debug("%s: ctrl %d qid %d: NULL DH group invalid "
"for secure channel concatenation\n", __func__,
ctrl->cntlid, req->sq->qid);
return NVME_AUTH_DHCHAP_FAILURE_CONCAT_MISMATCH;
}
pr_debug("%s: ctrl %d qid %d: selected DH group %s (%d)\n",
__func__, ctrl->cntlid, req->sq->qid,
nvme_auth_dhgroup_name(ctrl->dh_gid), ctrl->dh_gid);
return 0;
}
static u8 nvmet_auth_reply(struct nvmet_req *req, void *d, u32 tl)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmf_auth_dhchap_reply_data *data = d;
u16 dhvlen;
u8 *response;
if (tl < sizeof(*data))
return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
dhvlen = le16_to_cpu(data->dhvlen);
/* Validate that hl and dhvlen fit within the transfer length */
if (sizeof(*data) + 2 * (size_t)data->hl + dhvlen > tl)
return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %u\n",
__func__, ctrl->cntlid, req->sq->qid,
data->hl, data->cvalid, dhvlen);
if (dhvlen) {
if (!ctrl->dh_tfm)
return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
if (nvmet_auth_ctrl_sesskey(req, data->rval + 2 * data->hl,
dhvlen) < 0)
return NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE;
}
response = kmalloc(data->hl, GFP_KERNEL);
if (!response)
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
if (!ctrl->host_key) {
pr_warn("ctrl %d qid %d no host key\n",
ctrl->cntlid, req->sq->qid);
kfree(response);
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
}
if (nvmet_auth_host_hash(req, response, data->hl) < 0) {
pr_debug("ctrl %d qid %d host hash failed\n",
ctrl->cntlid, req->sq->qid);
kfree(response);
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
}
if (memcmp(data->rval, response, data->hl)) {
pr_info("ctrl %d qid %d host response mismatch\n",
ctrl->cntlid, req->sq->qid);
pr_debug("ctrl %d qid %d rval %*ph\n",
ctrl->cntlid, req->sq->qid, data->hl, data->rval);
pr_debug("ctrl %d qid %d response %*ph\n",
ctrl->cntlid, req->sq->qid, data->hl, response);
kfree(response);
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
}
kfree(response);
pr_debug("%s: ctrl %d qid %d host authenticated\n",
__func__, ctrl->cntlid, req->sq->qid);
if (!data->cvalid && ctrl->concat) {
pr_debug("%s: ctrl %d qid %d invalid challenge\n",
__func__, ctrl->cntlid, req->sq->qid);
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
}
req->sq->dhchap_s2 = le32_to_cpu(data->seqnum);
if (data->cvalid) {
req->sq->dhchap_c2 = kmemdup(data->rval + data->hl, data->hl,
GFP_KERNEL);
if (!req->sq->dhchap_c2)
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
pr_debug("%s: ctrl %d qid %d challenge %*ph\n",
__func__, ctrl->cntlid, req->sq->qid, data->hl,
req->sq->dhchap_c2);
}
/*
* NVMe Base Spec 2.2 section 8.3.4.5.4: DH-HMAC-CHAP_Reply message
* Sequence Number (SEQNUM): [ .. ]
* The value 0h is used to indicate that bidirectional authentication
* is not performed, but a challenge value C2 is carried in order to
* generate a pre-shared key (PSK) for subsequent establishment of a
* secure channel.
*/
if (req->sq->dhchap_s2 == 0) {
if (ctrl->concat)
nvmet_auth_insert_psk(req->sq);
req->sq->authenticated = true;
kfree(req->sq->dhchap_c2);
req->sq->dhchap_c2 = NULL;
} else if (!data->cvalid)
req->sq->authenticated = true;
return 0;
}
static u8 nvmet_auth_failure2(void *d)
{
struct nvmf_auth_dhchap_failure_data *data = d;
return data->rescode_exp;
}
u32 nvmet_auth_send_data_len(struct nvmet_req *req)
{
return le32_to_cpu(req->cmd->auth_send.tl);
}
void nvmet_execute_auth_send(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvmf_auth_dhchap_success2_data *data;
void *d;
u32 tl;
u16 status = 0;
u8 dhchap_status;
if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_send_command, secp);
goto done;
}
if (req->cmd->auth_send.spsp0 != 0x01) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_send_command, spsp0);
goto done;
}
if (req->cmd->auth_send.spsp1 != 0x01) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_send_command, spsp1);
goto done;
}
tl = nvmet_auth_send_data_len(req);
if (!tl) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_send_command, tl);
goto done;
}
if (!nvmet_check_transfer_len(req, tl)) {
pr_debug("%s: transfer length mismatch (%u)\n", __func__, tl);
return;
}
d = kmalloc(tl, GFP_KERNEL);
if (!d) {
status = NVME_SC_INTERNAL;
goto done;
}
status = nvmet_copy_from_sgl(req, 0, d, tl);
if (status)
goto done_kfree;
data = d;
pr_debug("%s: ctrl %d qid %d type %d id %d step %x\n", __func__,
ctrl->cntlid, req->sq->qid, data->auth_type, data->auth_id,
req->sq->dhchap_step);
if (data->auth_type != NVME_AUTH_COMMON_MESSAGES &&
data->auth_type != NVME_AUTH_DHCHAP_MESSAGES)
goto done_failure1;
if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) {
if (data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) {
/* Restart negotiation */
pr_debug("%s: ctrl %d qid %d reset negotiation\n",
__func__, ctrl->cntlid, req->sq->qid);
if (!req->sq->qid) {
dhchap_status = nvmet_setup_auth(ctrl, req->sq,
true);
if (dhchap_status) {
pr_err("ctrl %d qid 0 failed to setup re-authentication\n",
ctrl->cntlid);
req->sq->dhchap_status = dhchap_status;
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
goto done_kfree;
}
}
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE;
} else if (data->auth_id != req->sq->dhchap_step)
goto done_failure1;
/* Validate negotiation parameters */
dhchap_status = nvmet_auth_negotiate(req, d);
if (dhchap_status == 0)
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
else {
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
req->sq->dhchap_status = dhchap_status;
}
goto done_kfree;
}
if (data->auth_id != req->sq->dhchap_step) {
pr_debug("%s: ctrl %d qid %d step mismatch (%d != %d)\n",
__func__, ctrl->cntlid, req->sq->qid,
data->auth_id, req->sq->dhchap_step);
goto done_failure1;
}
if (le16_to_cpu(data->t_id) != req->sq->dhchap_tid) {
pr_debug("%s: ctrl %d qid %d invalid transaction %d (expected %d)\n",
__func__, ctrl->cntlid, req->sq->qid,
le16_to_cpu(data->t_id),
req->sq->dhchap_tid);
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
req->sq->dhchap_status =
NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
goto done_kfree;
}
switch (data->auth_id) {
case NVME_AUTH_DHCHAP_MESSAGE_REPLY:
dhchap_status = nvmet_auth_reply(req, d, tl);
if (dhchap_status == 0)
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
else {
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
req->sq->dhchap_status = dhchap_status;
}
goto done_kfree;
case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2:
if (ctrl->concat)
nvmet_auth_insert_psk(req->sq);
req->sq->authenticated = true;
pr_debug("%s: ctrl %d qid %d ctrl authenticated\n",
__func__, ctrl->cntlid, req->sq->qid);
goto done_kfree;
case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2:
dhchap_status = nvmet_auth_failure2(d);
if (dhchap_status) {
pr_warn("ctrl %d qid %d: authentication failed (%d)\n",
ctrl->cntlid, req->sq->qid, dhchap_status);
req->sq->dhchap_status = dhchap_status;
req->sq->authenticated = false;
}
goto done_kfree;
default:
req->sq->dhchap_status =
NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE;
req->sq->dhchap_step =
NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
req->sq->authenticated = false;
goto done_kfree;
}
done_failure1:
req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_MESSAGE;
req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE2;
done_kfree:
kfree(d);
done:
pr_debug("%s: ctrl %d qid %d dhchap status %x step %x\n", __func__,
ctrl->cntlid, req->sq->qid,
req->sq->dhchap_status, req->sq->dhchap_step);
if (status)
pr_debug("%s: ctrl %d qid %d nvme status %x error loc %d\n",
__func__, ctrl->cntlid, req->sq->qid,
status, req->error_loc);
if (req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2 &&
req->sq->dhchap_step != NVME_AUTH_DHCHAP_MESSAGE_FAILURE2) {
unsigned long auth_expire_secs = ctrl->kato ? ctrl->kato : 120;
mod_delayed_work(system_percpu_wq, &req->sq->auth_expired_work,
auth_expire_secs * HZ);
goto complete;
}
/* Final states, clear up variables */
nvmet_auth_sq_free(req->sq);
if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE2)
nvmet_ctrl_fatal_error(ctrl);
complete:
nvmet_req_complete(req, status);
}
static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al)
{
struct nvmf_auth_dhchap_challenge_data *data = d;
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int ret = 0;
int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
int data_size = sizeof(*d) + hash_len;
if (ctrl->dh_tfm)
data_size += ctrl->dh_keysize;
if (al < data_size) {
pr_debug("%s: buffer too small (al %d need %d)\n", __func__,
al, data_size);
return -EINVAL;
}
memset(data, 0, data_size);
req->sq->dhchap_s1 = nvme_auth_get_seqnum();
data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE;
data->t_id = cpu_to_le16(req->sq->dhchap_tid);
data->hashid = ctrl->shash_id;
data->hl = hash_len;
data->seqnum = cpu_to_le32(req->sq->dhchap_s1);
req->sq->dhchap_c1 = kmalloc(data->hl, GFP_KERNEL);
if (!req->sq->dhchap_c1)
return -ENOMEM;
get_random_bytes(req->sq->dhchap_c1, data->hl);
memcpy(data->cval, req->sq->dhchap_c1, data->hl);
if (ctrl->dh_tfm) {
data->dhgid = ctrl->dh_gid;
data->dhvlen = cpu_to_le16(ctrl->dh_keysize);
ret = nvmet_auth_ctrl_exponential(req, data->cval + data->hl,
ctrl->dh_keysize);
}
pr_debug("%s: ctrl %d qid %d seq %d transaction %d hl %d dhvlen %zu\n",
__func__, ctrl->cntlid, req->sq->qid, req->sq->dhchap_s1,
req->sq->dhchap_tid, data->hl, ctrl->dh_keysize);
return ret;
}
static int nvmet_auth_success1(struct nvmet_req *req, void *d, int al)
{
struct nvmf_auth_dhchap_success1_data *data = d;
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
WARN_ON(al < sizeof(*data));
memset(data, 0, sizeof(*data));
data->auth_type = NVME_AUTH_DHCHAP_MESSAGES;
data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1;
data->t_id = cpu_to_le16(req->sq->dhchap_tid);
data->hl = hash_len;
if (req->sq->dhchap_c2) {
if (!ctrl->ctrl_key) {
pr_warn("ctrl %d qid %d no ctrl key\n",
ctrl->cntlid, req->sq->qid);
return NVME_AUTH_DHCHAP_FAILURE_FAILED;
}
if (nvmet_auth_ctrl_hash(req, data->rval, data->hl))
return NVME_AUTH_DHCHAP_FAILURE_HASH_UNUSABLE;
data->rvalid = 1;
pr_debug("ctrl %d qid %d response %*ph\n",
ctrl->cntlid, req->sq->qid, data->hl, data->rval);
}
return 0;
}
static void nvmet_auth_failure1(struct nvmet_req *req, void *d, int al)
{
struct nvmf_auth_dhchap_failure_data *data = d;
WARN_ON(al < sizeof(*data));
data->auth_type = NVME_AUTH_COMMON_MESSAGES;
data->auth_id = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
data->t_id = cpu_to_le16(req->sq->dhchap_tid);
data->rescode = NVME_AUTH_DHCHAP_FAILURE_REASON_FAILED;
data->rescode_exp = req->sq->dhchap_status;
}
u32 nvmet_auth_receive_data_len(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u32 al = le32_to_cpu(req->cmd->auth_receive.al);
u32 min_len;
/*
* Reject too-short al before kmalloc(al), since the SUCCESS1 and
* FAILURE1/default builders write fixed response headers into it.
*/
switch (req->sq->dhchap_step) {
case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
return al;
case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
min_len = sizeof(struct nvmf_auth_dhchap_success1_data);
if (req->sq->dhchap_c2)
min_len += nvme_auth_hmac_hash_len(ctrl->shash_id);
break;
default:
min_len = sizeof(struct nvmf_auth_dhchap_failure_data);
break;
}
if (al < min_len)
return 0;
return al;
}
void nvmet_execute_auth_receive(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
void *d;
u32 al;
u16 status = 0;
if (req->cmd->auth_receive.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_receive_command, secp);
goto done;
}
if (req->cmd->auth_receive.spsp0 != 0x01) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_receive_command, spsp0);
goto done;
}
if (req->cmd->auth_receive.spsp1 != 0x01) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_receive_command, spsp1);
goto done;
}
al = nvmet_auth_receive_data_len(req);
if (!al) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc =
offsetof(struct nvmf_auth_receive_command, al);
goto done;
}
if (!nvmet_check_transfer_len(req, al)) {
pr_debug("%s: transfer length mismatch (%u)\n", __func__, al);
return;
}
d = kzalloc(al, GFP_KERNEL);
if (!d) {
status = NVME_SC_INTERNAL;
goto done;
}
pr_debug("%s: ctrl %d qid %d step %x\n", __func__,
ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
switch (req->sq->dhchap_step) {
case NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE:
if (nvmet_auth_challenge(req, d, al) < 0) {
pr_warn("ctrl %d qid %d: challenge error (%d)\n",
ctrl->cntlid, req->sq->qid, status);
status = NVME_SC_INTERNAL;
break;
}
req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_REPLY;
break;
case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1:
status = nvmet_auth_success1(req, d, al);
if (status) {
req->sq->dhchap_status = status;
req->sq->authenticated = false;
nvmet_auth_failure1(req, d, al);
pr_warn("ctrl %d qid %d: success1 status (%x)\n",
ctrl->cntlid, req->sq->qid,
req->sq->dhchap_status);
break;
}
req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2;
break;
case NVME_AUTH_DHCHAP_MESSAGE_FAILURE1:
req->sq->authenticated = false;
nvmet_auth_failure1(req, d, al);
pr_warn("ctrl %d qid %d failure1 (%x)\n",
ctrl->cntlid, req->sq->qid, req->sq->dhchap_status);
break;
default:
pr_warn("ctrl %d qid %d unhandled step (%d)\n",
ctrl->cntlid, req->sq->qid, req->sq->dhchap_step);
req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_FAILURE1;
req->sq->dhchap_status = NVME_AUTH_DHCHAP_FAILURE_FAILED;
nvmet_auth_failure1(req, d, al);
status = 0;
break;
}
status = nvmet_copy_to_sgl(req, 0, d, al);
kfree(d);
done:
if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2)
nvmet_auth_sq_free(req->sq);
else if (req->sq->dhchap_step == NVME_AUTH_DHCHAP_MESSAGE_FAILURE1) {
nvmet_auth_sq_free(req->sq);
nvmet_ctrl_fatal_error(ctrl);
}
nvmet_req_complete(req, status);
}
@@ -0,0 +1,430 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe Fabrics command implementation.
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/blkdev.h>
#include "nvmet.h"
static void nvmet_execute_prop_set(struct nvmet_req *req)
{
u64 val = le64_to_cpu(req->cmd->prop_set.value);
u16 status = 0;
if (!nvmet_check_transfer_len(req, 0))
return;
if (req->cmd->prop_set.attrib & 1) {
req->error_loc =
offsetof(struct nvmf_property_set_command, attrib);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
switch (le32_to_cpu(req->cmd->prop_set.offset)) {
case NVME_REG_CC:
nvmet_update_cc(req->sq->ctrl, val);
break;
default:
req->error_loc =
offsetof(struct nvmf_property_set_command, offset);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
out:
nvmet_req_complete(req, status);
}
static void nvmet_execute_prop_get(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u16 status = 0;
u64 val = 0;
if (!nvmet_check_transfer_len(req, 0))
return;
if (req->cmd->prop_get.attrib & 1) {
switch (le32_to_cpu(req->cmd->prop_get.offset)) {
case NVME_REG_CAP:
val = ctrl->cap;
break;
default:
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
break;
}
} else {
switch (le32_to_cpu(req->cmd->prop_get.offset)) {
case NVME_REG_VS:
val = ctrl->subsys->ver;
break;
case NVME_REG_CC:
val = ctrl->cc;
break;
case NVME_REG_CSTS:
val = ctrl->csts;
break;
case NVME_REG_CRTO:
val = NVME_CAP_TIMEOUT(ctrl->csts);
break;
default:
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
break;
}
}
if (status && req->cmd->prop_get.attrib & 1) {
req->error_loc =
offsetof(struct nvmf_property_get_command, offset);
} else {
req->error_loc =
offsetof(struct nvmf_property_get_command, attrib);
}
req->cqe->result.u64 = cpu_to_le64(val);
nvmet_req_complete(req, status);
}
u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->fabrics.fctype) {
#ifdef CONFIG_NVME_TARGET_AUTH
case nvme_fabrics_type_auth_send:
return nvmet_auth_send_data_len(req);
case nvme_fabrics_type_auth_receive:
return nvmet_auth_receive_data_len(req);
#endif
default:
return 0;
}
}
u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->fabrics.fctype) {
case nvme_fabrics_type_property_set:
req->execute = nvmet_execute_prop_set;
break;
case nvme_fabrics_type_property_get:
req->execute = nvmet_execute_prop_get;
break;
#ifdef CONFIG_NVME_TARGET_AUTH
case nvme_fabrics_type_auth_send:
req->execute = nvmet_execute_auth_send;
break;
case nvme_fabrics_type_auth_receive:
req->execute = nvmet_execute_auth_receive;
break;
#endif
default:
pr_debug("received unknown capsule type 0x%x\n",
cmd->fabrics.fctype);
req->error_loc = offsetof(struct nvmf_common_command, fctype);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
return 0;
}
u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->fabrics.fctype) {
#ifdef CONFIG_NVME_TARGET_AUTH
case nvme_fabrics_type_auth_send:
return nvmet_auth_send_data_len(req);
case nvme_fabrics_type_auth_receive:
return nvmet_auth_receive_data_len(req);
#endif
default:
return 0;
}
}
u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->fabrics.fctype) {
#ifdef CONFIG_NVME_TARGET_AUTH
case nvme_fabrics_type_auth_send:
req->execute = nvmet_execute_auth_send;
break;
case nvme_fabrics_type_auth_receive:
req->execute = nvmet_execute_auth_receive;
break;
#endif
default:
pr_debug("received unknown capsule type 0x%x\n",
cmd->fabrics.fctype);
req->error_loc = offsetof(struct nvmf_common_command, fctype);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
return 0;
}
static u16 nvmet_install_queue(struct nvmet_ctrl *ctrl, struct nvmet_req *req)
{
struct nvmf_connect_command *c = &req->cmd->connect;
u16 qid = le16_to_cpu(c->qid);
u16 sqsize = le16_to_cpu(c->sqsize);
struct nvmet_ctrl *old;
u16 mqes = NVME_CAP_MQES(ctrl->cap);
u16 ret;
if (!sqsize) {
pr_warn("queue size zero!\n");
req->error_loc = offsetof(struct nvmf_connect_command, sqsize);
req->cqe->result.u32 = IPO_IATTR_CONNECT_SQE(sqsize);
ret = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
goto err;
}
if (ctrl->sqs[qid] != NULL) {
pr_warn("qid %u has already been created\n", qid);
req->error_loc = offsetof(struct nvmf_connect_command, qid);
return NVME_SC_CMD_SEQ_ERROR | NVME_STATUS_DNR;
}
/* for fabrics, this value applies to only the I/O Submission Queues */
if (qid && sqsize > mqes) {
pr_warn("sqsize %u is larger than MQES supported %u cntlid %d\n",
sqsize, mqes, ctrl->cntlid);
req->error_loc = offsetof(struct nvmf_connect_command, sqsize);
req->cqe->result.u32 = IPO_IATTR_CONNECT_SQE(sqsize);
return NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
}
old = cmpxchg(&req->sq->ctrl, NULL, ctrl);
if (old) {
pr_warn("queue already connected!\n");
req->error_loc = offsetof(struct nvmf_connect_command, opcode);
return NVME_SC_CONNECT_CTRL_BUSY | NVME_STATUS_DNR;
}
kref_get(&ctrl->ref);
old = cmpxchg(&req->cq->ctrl, NULL, ctrl);
if (old) {
pr_warn("queue already connected!\n");
req->error_loc = offsetof(struct nvmf_connect_command, opcode);
return NVME_SC_CONNECT_CTRL_BUSY | NVME_STATUS_DNR;
}
/* note: convert queue size from 0's-based value to 1's-based value */
nvmet_cq_setup(ctrl, req->cq, qid, sqsize + 1);
nvmet_sq_setup(ctrl, req->sq, qid, sqsize + 1);
if (c->cattr & NVME_CONNECT_DISABLE_SQFLOW) {
req->sq->sqhd_disabled = true;
req->cqe->sq_head = cpu_to_le16(0xffff);
}
if (ctrl->ops->install_queue) {
ret = ctrl->ops->install_queue(req->sq);
if (ret) {
pr_err("failed to install queue %d cntlid %d ret %x\n",
qid, ctrl->cntlid, ret);
ctrl->sqs[qid] = NULL;
goto err;
}
}
return 0;
err:
req->sq->ctrl = NULL;
return ret;
}
static u32 nvmet_connect_result(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq)
{
bool needs_auth = nvmet_has_auth(ctrl, sq);
key_serial_t keyid = nvmet_queue_tls_keyid(sq);
/* Do not authenticate I/O queues */
if (sq->qid)
needs_auth = false;
if (keyid)
pr_debug("%s: ctrl %d qid %d should %sauthenticate, tls psk %08x\n",
__func__, ctrl->cntlid, sq->qid,
needs_auth ? "" : "not ", keyid);
else
pr_debug("%s: ctrl %d qid %d should %sauthenticate%s\n",
__func__, ctrl->cntlid, sq->qid,
needs_auth ? "" : "not ",
ctrl->concat ? ", secure concatenation" : "");
return (u32)ctrl->cntlid |
(needs_auth ? NVME_CONNECT_AUTHREQ_ATR : 0);
}
static void nvmet_execute_admin_connect(struct nvmet_req *req)
{
struct nvmf_connect_command *c = &req->cmd->connect;
struct nvmf_connect_data *d;
struct nvmet_ctrl *ctrl = NULL;
struct nvmet_alloc_ctrl_args args = {
.port = req->port,
.sq = req->sq,
.ops = req->ops,
.p2p_client = req->p2p_client,
.kato = le32_to_cpu(c->kato),
};
if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
return;
d = kmalloc_obj(*d);
if (!d) {
args.status = NVME_SC_INTERNAL;
goto complete;
}
args.status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d));
if (args.status)
goto out;
if (c->recfmt != 0) {
pr_warn("invalid connect version (%d).\n",
le16_to_cpu(c->recfmt));
args.error_loc = offsetof(struct nvmf_connect_command, recfmt);
args.status = NVME_SC_CONNECT_FORMAT | NVME_STATUS_DNR;
goto out;
}
if (unlikely(d->cntlid != cpu_to_le16(0xffff))) {
pr_warn("connect attempt for invalid controller ID %#x\n",
d->cntlid);
args.status = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
args.result = IPO_IATTR_CONNECT_DATA(cntlid);
goto out;
}
d->subsysnqn[NVMF_NQN_FIELD_LEN - 1] = '\0';
d->hostnqn[NVMF_NQN_FIELD_LEN - 1] = '\0';
args.subsysnqn = d->subsysnqn;
args.hostnqn = d->hostnqn;
args.hostid = &d->hostid;
args.kato = le32_to_cpu(c->kato);
ctrl = nvmet_alloc_ctrl(&args);
if (!ctrl)
goto out;
args.status = nvmet_install_queue(ctrl, req);
if (args.status) {
nvmet_ctrl_put(ctrl);
goto out;
}
args.result = cpu_to_le32(nvmet_connect_result(ctrl, req->sq));
out:
kfree(d);
complete:
req->error_loc = args.error_loc;
req->cqe->result.u32 = args.result;
nvmet_req_complete(req, args.status);
}
static void nvmet_execute_io_connect(struct nvmet_req *req)
{
struct nvmf_connect_command *c = &req->cmd->connect;
struct nvmf_connect_data *d;
struct nvmet_ctrl *ctrl;
u16 qid = le16_to_cpu(c->qid);
u16 status;
if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data)))
return;
d = kmalloc_obj(*d);
if (!d) {
status = NVME_SC_INTERNAL;
goto complete;
}
status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d));
if (status)
goto out;
if (c->recfmt != 0) {
pr_warn("invalid connect version (%d).\n",
le16_to_cpu(c->recfmt));
status = NVME_SC_CONNECT_FORMAT | NVME_STATUS_DNR;
goto out;
}
d->subsysnqn[NVMF_NQN_FIELD_LEN - 1] = '\0';
d->hostnqn[NVMF_NQN_FIELD_LEN - 1] = '\0';
ctrl = nvmet_ctrl_find_get(d->subsysnqn, d->hostnqn,
le16_to_cpu(d->cntlid), req);
if (!ctrl) {
status = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
goto out;
}
if (unlikely(qid > ctrl->subsys->max_qid)) {
pr_warn("invalid queue id (%d)\n", qid);
status = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
req->cqe->result.u32 = IPO_IATTR_CONNECT_SQE(qid);
goto out_ctrl_put;
}
status = nvmet_install_queue(ctrl, req);
if (status)
goto out_ctrl_put;
pr_debug("adding queue %d to ctrl %d.\n", qid, ctrl->cntlid);
req->cqe->result.u32 = cpu_to_le32(nvmet_connect_result(ctrl, req->sq));
out:
kfree(d);
complete:
nvmet_req_complete(req, status);
return;
out_ctrl_put:
nvmet_ctrl_put(ctrl);
goto out;
}
u32 nvmet_connect_cmd_data_len(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
if (!nvme_is_fabrics(cmd) ||
cmd->fabrics.fctype != nvme_fabrics_type_connect)
return 0;
return sizeof(struct nvmf_connect_data);
}
u16 nvmet_parse_connect_cmd(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
if (!nvme_is_fabrics(cmd)) {
pr_debug("invalid command 0x%x on unconnected queue.\n",
cmd->fabrics.opcode);
req->error_loc = offsetof(struct nvme_common_command, opcode);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
if (cmd->fabrics.fctype != nvme_fabrics_type_connect) {
pr_debug("invalid capsule type 0x%x on unconnected queue.\n",
cmd->fabrics.fctype);
req->error_loc = offsetof(struct nvmf_common_command, fctype);
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
if (cmd->connect.qid == 0)
req->execute = nvmet_execute_admin_connect;
else
req->execute = nvmet_execute_io_connect;
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,475 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe I/O command implementation.
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/blkdev.h>
#include <linux/blk-integrity.h>
#include <linux/memremap.h>
#include <linux/module.h>
#include "nvmet.h"
void nvmet_bdev_set_limits(struct block_device *bdev, struct nvme_id_ns *id)
{
/* Logical blocks per physical block, 0's based. */
const __le16 lpp0b = to0based(bdev_physical_block_size(bdev) /
bdev_logical_block_size(bdev));
/*
* For NVMe 1.2 and later, bit 1 indicates that the fields NAWUN,
* NAWUPF, and NACWU are defined for this namespace and should be
* used by the host for this namespace instead of the AWUN, AWUPF,
* and ACWU fields in the Identify Controller data structure. If
* any of these fields are zero that means that the corresponding
* field from the identify controller data structure should be used.
*/
id->nsfeat |= 1 << 1;
id->nawun = lpp0b;
id->nawupf = lpp0b;
id->nacwu = lpp0b;
/*
* OPTPERF = 11b indicates that the fields NPWG, NPWA, NPDG, NPDA,
* NPDGL, NPDAL, and NOWS are defined for this namespace and should be
* used by the host for I/O optimization.
*/
id->nsfeat |= 0x3 << NVME_NS_FEAT_OPTPERF_SHIFT;
/* NPWG = Namespace Preferred Write Granularity. 0's based */
id->npwg = to0based(bdev_io_min(bdev) / bdev_logical_block_size(bdev));
/* NPWA = Namespace Preferred Write Alignment. 0's based */
id->npwa = id->npwg;
/* NPDG = Namespace Preferred Deallocate Granularity. 0's based */
id->npdg = to0based(bdev_discard_granularity(bdev) /
bdev_logical_block_size(bdev));
/* NPDG = Namespace Preferred Deallocate Alignment */
id->npda = id->npdg;
/* NOWS = Namespace Optimal Write Size */
id->nows = to0based(bdev_io_opt(bdev) / bdev_logical_block_size(bdev));
/* Set WZDS and DRB if device supports unmapped write zeroes */
if (bdev_write_zeroes_unmap_sectors(bdev))
id->dlfeat = (1 << 3) | 0x1;
}
void nvmet_bdev_set_nvm_limits(struct block_device *bdev,
struct nvme_id_ns_nvm *id)
{
/*
* NPDGL = Namespace Preferred Deallocate Granularity Large
* NPDAL = Namespace Preferred Deallocate Alignment Large
*/
id->npdgl = id->npdal = cpu_to_le32(bdev_discard_granularity(bdev) /
bdev_logical_block_size(bdev));
}
void nvmet_bdev_ns_disable(struct nvmet_ns *ns)
{
if (ns->bdev_file) {
fput(ns->bdev_file);
ns->bdev = NULL;
ns->bdev_file = NULL;
}
}
static void nvmet_bdev_ns_enable_integrity(struct nvmet_ns *ns)
{
struct blk_integrity *bi = bdev_get_integrity(ns->bdev);
if (!bi)
return;
if (bi->csum_type == BLK_INTEGRITY_CSUM_CRC) {
ns->metadata_size = bi->metadata_size;
if (bi->flags & BLK_INTEGRITY_REF_TAG)
ns->pi_type = NVME_NS_DPS_PI_TYPE1;
else
ns->pi_type = NVME_NS_DPS_PI_TYPE3;
} else {
ns->metadata_size = 0;
}
}
int nvmet_bdev_ns_enable(struct nvmet_ns *ns)
{
int ret;
/*
* When buffered_io namespace attribute is enabled that means user want
* this block device to be used as a file, so block device can take
* an advantage of cache.
*/
if (ns->buffered_io)
return -ENOTBLK;
ns->bdev_file = bdev_file_open_by_path(ns->device_path,
BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL);
if (IS_ERR(ns->bdev_file)) {
ret = PTR_ERR(ns->bdev_file);
if (ret != -ENOTBLK) {
pr_err("failed to open block device %s: (%d)\n",
ns->device_path, ret);
}
ns->bdev_file = NULL;
return ret;
}
ns->bdev = file_bdev(ns->bdev_file);
ns->size = bdev_nr_bytes(ns->bdev);
ns->blksize_shift = blksize_bits(bdev_logical_block_size(ns->bdev));
ns->pi_type = 0;
ns->metadata_size = 0;
if (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY))
nvmet_bdev_ns_enable_integrity(ns);
if (bdev_is_zoned(ns->bdev)) {
if (!nvmet_bdev_zns_enable(ns)) {
nvmet_bdev_ns_disable(ns);
return -EINVAL;
}
ns->csi = NVME_CSI_ZNS;
}
return 0;
}
void nvmet_bdev_ns_revalidate(struct nvmet_ns *ns)
{
ns->size = bdev_nr_bytes(ns->bdev);
}
u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts)
{
u16 status = NVME_SC_SUCCESS;
if (likely(blk_sts == BLK_STS_OK))
return status;
/*
* Right now there exists M : 1 mapping between block layer error
* to the NVMe status code (see nvme_error_status()). For consistency,
* when we reverse map we use most appropriate NVMe Status code from
* the group of the NVMe status codes used in the nvme_error_status().
*/
switch (blk_sts) {
case BLK_STS_NOSPC:
status = NVME_SC_CAP_EXCEEDED | NVME_STATUS_DNR;
req->error_loc = offsetof(struct nvme_rw_command, length);
break;
case BLK_STS_TARGET:
status = NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
req->error_loc = offsetof(struct nvme_rw_command, slba);
break;
case BLK_STS_NOTSUPP:
status = NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
req->error_loc = offsetof(struct nvme_common_command, opcode);
break;
case BLK_STS_MEDIUM:
status = NVME_SC_ACCESS_DENIED;
req->error_loc = offsetof(struct nvme_rw_command, nsid);
break;
case BLK_STS_IOERR:
default:
status = NVME_SC_INTERNAL | NVME_STATUS_DNR;
req->error_loc = offsetof(struct nvme_common_command, opcode);
}
switch (req->cmd->common.opcode) {
case nvme_cmd_read:
case nvme_cmd_write:
req->error_slba = le64_to_cpu(req->cmd->rw.slba);
break;
case nvme_cmd_write_zeroes:
req->error_slba =
le64_to_cpu(req->cmd->write_zeroes.slba);
break;
default:
req->error_slba = 0;
}
return status;
}
static void nvmet_bio_done(struct bio *bio)
{
struct nvmet_req *req = bio->bi_private;
blk_status_t blk_status = bio->bi_status;
nvmet_req_bio_put(req, bio);
nvmet_req_complete(req, blk_to_nvme_status(req, blk_status));
}
#ifdef CONFIG_BLK_DEV_INTEGRITY
static int nvmet_bdev_alloc_bip(struct nvmet_req *req, struct bio *bio,
struct sg_mapping_iter *miter)
{
struct blk_integrity *bi;
struct bio_integrity_payload *bip;
int rc;
size_t resid, len;
bi = bdev_get_integrity(req->ns->bdev);
if (unlikely(!bi)) {
pr_err("Unable to locate bio_integrity\n");
return -ENODEV;
}
bip = bio_integrity_alloc(bio, GFP_NOIO,
bio_max_segs(req->metadata_sg_cnt));
if (IS_ERR(bip)) {
pr_err("Unable to allocate bio_integrity_payload\n");
return PTR_ERR(bip);
}
/* virtual start sector must be in integrity interval units */
bip_set_seed(bip, bio->bi_iter.bi_sector >>
(bi->interval_exp - SECTOR_SHIFT));
resid = bio_integrity_bytes(bi, bio_sectors(bio));
while (resid > 0 && sg_miter_next(miter)) {
len = min_t(size_t, miter->length, resid);
rc = bio_integrity_add_page(bio, miter->page, len,
offset_in_page(miter->addr));
if (unlikely(rc != len)) {
pr_err("bio_integrity_add_page() failed; %d\n", rc);
sg_miter_stop(miter);
return -ENOMEM;
}
resid -= len;
if (len < miter->length)
miter->consumed -= miter->length - len;
}
sg_miter_stop(miter);
return 0;
}
#else
static int nvmet_bdev_alloc_bip(struct nvmet_req *req, struct bio *bio,
struct sg_mapping_iter *miter)
{
return -EINVAL;
}
#endif /* CONFIG_BLK_DEV_INTEGRITY */
static void nvmet_bdev_execute_rw(struct nvmet_req *req)
{
unsigned int sg_cnt = req->sg_cnt;
struct bio *bio;
struct scatterlist *sg;
struct blk_plug plug;
sector_t sector;
blk_opf_t opf;
int i, rc;
struct sg_mapping_iter prot_miter;
unsigned int iter_flags;
unsigned int total_len = nvmet_rw_data_len(req) + req->metadata_len;
if (!nvmet_check_transfer_len(req, total_len))
return;
if (!req->sg_cnt) {
nvmet_req_complete(req, 0);
return;
}
if (req->cmd->rw.opcode == nvme_cmd_write) {
opf = REQ_OP_WRITE | REQ_SYNC | REQ_IDLE;
if (req->cmd->rw.control & cpu_to_le16(NVME_RW_FUA))
opf |= REQ_FUA;
iter_flags = SG_MITER_TO_SG;
} else {
opf = REQ_OP_READ;
iter_flags = SG_MITER_FROM_SG;
}
if (req->cmd->rw.control & cpu_to_le16(NVME_RW_LR))
opf |= REQ_FAILFAST_DEV;
if (is_pci_p2pdma_page(sg_page(req->sg)))
opf |= REQ_NOMERGE;
sector = nvmet_lba_to_sect(req->ns, req->cmd->rw.slba);
if (nvmet_use_inline_bvec(req)) {
bio = &req->b.inline_bio;
bio_init(bio, req->ns->bdev, req->inline_bvec,
ARRAY_SIZE(req->inline_bvec), opf);
} else {
bio = bio_alloc(req->ns->bdev, bio_max_segs(sg_cnt), opf,
GFP_KERNEL);
}
bio->bi_iter.bi_sector = sector;
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
blk_start_plug(&plug);
if (req->metadata_len)
sg_miter_start(&prot_miter, req->metadata_sg,
req->metadata_sg_cnt, iter_flags);
for_each_sg(req->sg, sg, req->sg_cnt, i) {
while (bio_add_page(bio, sg_page(sg), sg->length, sg->offset)
!= sg->length) {
struct bio *prev = bio;
if (req->metadata_len) {
rc = nvmet_bdev_alloc_bip(req, bio,
&prot_miter);
if (unlikely(rc)) {
bio_io_error(bio);
return;
}
}
bio = bio_alloc(req->ns->bdev, bio_max_segs(sg_cnt),
opf, GFP_KERNEL);
bio->bi_iter.bi_sector = sector;
bio_chain(bio, prev);
submit_bio(prev);
}
sector += sg->length >> 9;
sg_cnt--;
}
if (req->metadata_len) {
rc = nvmet_bdev_alloc_bip(req, bio, &prot_miter);
if (unlikely(rc)) {
bio_io_error(bio);
return;
}
}
submit_bio(bio);
blk_finish_plug(&plug);
}
static void nvmet_bdev_execute_flush(struct nvmet_req *req)
{
struct bio *bio = &req->b.inline_bio;
if (!bdev_write_cache(req->ns->bdev)) {
nvmet_req_complete(req, NVME_SC_SUCCESS);
return;
}
if (!nvmet_check_transfer_len(req, 0))
return;
bio_init(bio, req->ns->bdev, req->inline_bvec,
ARRAY_SIZE(req->inline_bvec), REQ_OP_WRITE | REQ_PREFLUSH);
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
submit_bio(bio);
}
u16 nvmet_bdev_flush(struct nvmet_req *req)
{
if (!bdev_write_cache(req->ns->bdev))
return 0;
if (blkdev_issue_flush(req->ns->bdev))
return NVME_SC_INTERNAL | NVME_STATUS_DNR;
return 0;
}
static void nvmet_bdev_execute_discard(struct nvmet_req *req)
{
struct nvmet_ns *ns = req->ns;
struct nvme_dsm_range range;
struct bio *bio = NULL;
sector_t nr_sects;
int i;
u16 status = NVME_SC_SUCCESS;
for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
status = nvmet_copy_from_sgl(req, i * sizeof(range), &range,
sizeof(range));
if (status)
break;
nr_sects = le32_to_cpu(range.nlb) << (ns->blksize_shift - 9);
__blkdev_issue_discard(ns->bdev,
nvmet_lba_to_sect(ns, range.slba), nr_sects,
GFP_KERNEL, &bio);
}
if (bio) {
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
if (status)
bio_io_error(bio);
else
submit_bio(bio);
} else {
nvmet_req_complete(req, status);
}
}
static void nvmet_bdev_execute_dsm(struct nvmet_req *req)
{
if (!nvmet_check_data_len_lte(req, nvmet_dsm_len(req)))
return;
switch (le32_to_cpu(req->cmd->dsm.attributes)) {
case NVME_DSMGMT_AD:
nvmet_bdev_execute_discard(req);
return;
case NVME_DSMGMT_IDR:
case NVME_DSMGMT_IDW:
default:
/* Not supported yet */
nvmet_req_complete(req, 0);
return;
}
}
static void nvmet_bdev_execute_write_zeroes(struct nvmet_req *req)
{
struct nvme_write_zeroes_cmd *write_zeroes = &req->cmd->write_zeroes;
struct bio *bio = NULL;
sector_t sector;
sector_t nr_sector;
int ret;
if (!nvmet_check_transfer_len(req, 0))
return;
sector = nvmet_lba_to_sect(req->ns, write_zeroes->slba);
nr_sector = (((sector_t)le16_to_cpu(write_zeroes->length) + 1) <<
(req->ns->blksize_shift - 9));
ret = __blkdev_issue_zeroout(req->ns->bdev, sector, nr_sector,
GFP_KERNEL, &bio, 0);
if (bio) {
bio->bi_private = req;
bio->bi_end_io = nvmet_bio_done;
submit_bio(bio);
} else {
nvmet_req_complete(req, errno_to_nvme_status(req, ret));
}
}
u16 nvmet_bdev_parse_io_cmd(struct nvmet_req *req)
{
switch (req->cmd->common.opcode) {
case nvme_cmd_read:
case nvme_cmd_write:
req->execute = nvmet_bdev_execute_rw;
if (req->sq->ctrl->pi_support && nvmet_ns_has_pi(req->ns))
req->metadata_len = nvmet_rw_metadata_len(req);
return 0;
case nvme_cmd_flush:
req->execute = nvmet_bdev_execute_flush;
return 0;
case nvme_cmd_dsm:
req->execute = nvmet_bdev_execute_dsm;
return 0;
case nvme_cmd_write_zeroes:
req->execute = nvmet_bdev_execute_write_zeroes;
return 0;
default:
return nvmet_report_invalid_opcode(req);
}
}
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe Over Fabrics Target File I/O commands implementation.
* Copyright (c) 2017-2018 Western Digital Corporation or its
* affiliates.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/uio.h>
#include <linux/falloc.h>
#include <linux/file.h>
#include <linux/fs.h>
#include "nvmet.h"
#define NVMET_MIN_MPOOL_OBJ 16
void nvmet_file_ns_revalidate(struct nvmet_ns *ns)
{
ns->size = i_size_read(ns->file->f_mapping->host);
}
void nvmet_file_ns_disable(struct nvmet_ns *ns)
{
if (ns->file) {
if (ns->buffered_io)
flush_workqueue(buffered_io_wq);
mempool_destroy(ns->bvec_pool);
ns->bvec_pool = NULL;
fput(ns->file);
ns->file = NULL;
}
}
int nvmet_file_ns_enable(struct nvmet_ns *ns)
{
int flags = O_RDWR | O_LARGEFILE;
int ret = 0;
if (!ns->buffered_io)
flags |= O_DIRECT;
ns->file = filp_open(ns->device_path, flags, 0);
if (IS_ERR(ns->file)) {
ret = PTR_ERR(ns->file);
pr_err("failed to open file %s: (%d)\n",
ns->device_path, ret);
ns->file = NULL;
return ret;
}
nvmet_file_ns_revalidate(ns);
/*
* i_blkbits can be greater than the universally accepted upper bound,
* so make sure we export a sane namespace lba_shift.
*/
ns->blksize_shift = min_t(u8,
file_inode(ns->file)->i_blkbits, 12);
ns->bvec_pool = mempool_create(NVMET_MIN_MPOOL_OBJ, mempool_alloc_slab,
mempool_free_slab, nvmet_bvec_cache);
if (!ns->bvec_pool) {
ret = -ENOMEM;
goto err;
}
return ret;
err:
fput(ns->file);
ns->file = NULL;
ns->size = 0;
ns->blksize_shift = 0;
return ret;
}
static ssize_t nvmet_file_submit_bvec(struct nvmet_req *req, loff_t pos,
unsigned long nr_segs, size_t count, int ki_flags)
{
struct kiocb *iocb = &req->f.iocb;
ssize_t (*call_iter)(struct kiocb *iocb, struct iov_iter *iter);
struct iov_iter iter;
int rw;
if (req->cmd->rw.opcode == nvme_cmd_write) {
if (req->cmd->rw.control & cpu_to_le16(NVME_RW_FUA))
ki_flags |= IOCB_DSYNC;
call_iter = req->ns->file->f_op->write_iter;
rw = ITER_SOURCE;
} else {
call_iter = req->ns->file->f_op->read_iter;
rw = ITER_DEST;
}
iov_iter_bvec(&iter, rw, req->f.bvec, nr_segs, count);
iocb->ki_pos = pos;
iocb->ki_filp = req->ns->file;
iocb->ki_flags = ki_flags | iocb->ki_filp->f_iocb_flags;
return call_iter(iocb, &iter);
}
static void nvmet_file_io_done(struct kiocb *iocb, long ret)
{
struct nvmet_req *req = container_of(iocb, struct nvmet_req, f.iocb);
u16 status = NVME_SC_SUCCESS;
if (req->f.bvec != req->inline_bvec) {
if (likely(req->f.mpool_alloc == false))
kfree(req->f.bvec);
else
mempool_free(req->f.bvec, req->ns->bvec_pool);
}
if (unlikely(ret != req->transfer_len))
status = errno_to_nvme_status(req, ret);
nvmet_req_complete(req, status);
}
static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
{
ssize_t nr_bvec = req->sg_cnt;
unsigned long bv_cnt = 0;
bool is_sync = false;
size_t len = 0, total_len = 0;
ssize_t ret = 0;
loff_t pos;
int i;
struct scatterlist *sg;
if (req->f.mpool_alloc && nr_bvec > NVMET_MAX_MPOOL_BVEC)
is_sync = true;
pos = le64_to_cpu(req->cmd->rw.slba) << req->ns->blksize_shift;
if (unlikely(pos + req->transfer_len > req->ns->size)) {
nvmet_req_complete(req, errno_to_nvme_status(req, -ENOSPC));
return true;
}
memset(&req->f.iocb, 0, sizeof(struct kiocb));
for_each_sg(req->sg, sg, req->sg_cnt, i) {
bvec_set_page(&req->f.bvec[bv_cnt], sg_page(sg), sg->length,
sg->offset);
len += req->f.bvec[bv_cnt].bv_len;
total_len += req->f.bvec[bv_cnt].bv_len;
bv_cnt++;
WARN_ON_ONCE((nr_bvec - 1) < 0);
if (unlikely(is_sync) &&
(nr_bvec - 1 == 0 || bv_cnt == NVMET_MAX_MPOOL_BVEC)) {
ret = nvmet_file_submit_bvec(req, pos, bv_cnt, len, 0);
if (ret < 0)
goto complete;
pos += len;
bv_cnt = 0;
len = 0;
}
nr_bvec--;
}
if (WARN_ON_ONCE(total_len != req->transfer_len)) {
ret = -EIO;
goto complete;
}
if (unlikely(is_sync)) {
ret = total_len;
goto complete;
}
/*
* A NULL ki_complete ask for synchronous execution, which we want
* for the IOCB_NOWAIT case.
*/
if (!(ki_flags & IOCB_NOWAIT))
req->f.iocb.ki_complete = nvmet_file_io_done;
ret = nvmet_file_submit_bvec(req, pos, bv_cnt, total_len, ki_flags);
switch (ret) {
case -EIOCBQUEUED:
return true;
case -EAGAIN:
if (WARN_ON_ONCE(!(ki_flags & IOCB_NOWAIT)))
goto complete;
return false;
case -EOPNOTSUPP:
/*
* For file systems returning error -EOPNOTSUPP, handle
* IOCB_NOWAIT error case separately and retry without
* IOCB_NOWAIT.
*/
if ((ki_flags & IOCB_NOWAIT))
return false;
break;
}
complete:
nvmet_file_io_done(&req->f.iocb, ret);
return true;
}
static void nvmet_file_buffered_io_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, f.work);
nvmet_file_execute_io(req, 0);
}
static void nvmet_file_submit_buffered_io(struct nvmet_req *req)
{
INIT_WORK(&req->f.work, nvmet_file_buffered_io_work);
queue_work(buffered_io_wq, &req->f.work);
}
static void nvmet_file_execute_rw(struct nvmet_req *req)
{
ssize_t nr_bvec = req->sg_cnt;
if (!nvmet_check_transfer_len(req, nvmet_rw_data_len(req)))
return;
if (!req->sg_cnt || !nr_bvec) {
nvmet_req_complete(req, 0);
return;
}
if (nr_bvec > NVMET_MAX_INLINE_BIOVEC)
req->f.bvec = kmalloc_objs(struct bio_vec, nr_bvec);
else
req->f.bvec = req->inline_bvec;
if (unlikely(!req->f.bvec)) {
/* fallback under memory pressure */
req->f.bvec = mempool_alloc(req->ns->bvec_pool, GFP_KERNEL);
req->f.mpool_alloc = true;
} else
req->f.mpool_alloc = false;
if (req->ns->buffered_io) {
if (likely(!req->f.mpool_alloc) &&
(req->ns->file->f_mode & FMODE_NOWAIT) &&
nvmet_file_execute_io(req, IOCB_NOWAIT))
return;
nvmet_file_submit_buffered_io(req);
} else
nvmet_file_execute_io(req, 0);
}
u16 nvmet_file_flush(struct nvmet_req *req)
{
return errno_to_nvme_status(req, vfs_fsync(req->ns->file, 1));
}
static void nvmet_file_flush_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, f.work);
nvmet_req_complete(req, nvmet_file_flush(req));
}
static void nvmet_file_execute_flush(struct nvmet_req *req)
{
if (!nvmet_check_transfer_len(req, 0))
return;
INIT_WORK(&req->f.work, nvmet_file_flush_work);
queue_work(nvmet_wq, &req->f.work);
}
static void nvmet_file_execute_discard(struct nvmet_req *req)
{
int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
struct nvme_dsm_range range;
loff_t offset, len;
u16 status = 0;
int ret;
int i;
for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
status = nvmet_copy_from_sgl(req, i * sizeof(range), &range,
sizeof(range));
if (status)
break;
offset = le64_to_cpu(range.slba) << req->ns->blksize_shift;
len = le32_to_cpu(range.nlb);
len <<= req->ns->blksize_shift;
if (offset + len > req->ns->size) {
req->error_slba = le64_to_cpu(range.slba);
status = errno_to_nvme_status(req, -ENOSPC);
break;
}
ret = vfs_fallocate(req->ns->file, mode, offset, len);
if (ret && ret != -EOPNOTSUPP) {
req->error_slba = le64_to_cpu(range.slba);
status = errno_to_nvme_status(req, ret);
break;
}
}
nvmet_req_complete(req, status);
}
static void nvmet_file_dsm_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, f.work);
switch (le32_to_cpu(req->cmd->dsm.attributes)) {
case NVME_DSMGMT_AD:
nvmet_file_execute_discard(req);
return;
case NVME_DSMGMT_IDR:
case NVME_DSMGMT_IDW:
default:
/* Not supported yet */
nvmet_req_complete(req, 0);
return;
}
}
static void nvmet_file_execute_dsm(struct nvmet_req *req)
{
if (!nvmet_check_data_len_lte(req, nvmet_dsm_len(req)))
return;
INIT_WORK(&req->f.work, nvmet_file_dsm_work);
queue_work(nvmet_wq, &req->f.work);
}
static void nvmet_file_write_zeroes_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, f.work);
struct nvme_write_zeroes_cmd *write_zeroes = &req->cmd->write_zeroes;
int mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
loff_t offset;
loff_t len;
int ret;
offset = le64_to_cpu(write_zeroes->slba) << req->ns->blksize_shift;
len = (((sector_t)le16_to_cpu(write_zeroes->length) + 1) <<
req->ns->blksize_shift);
if (unlikely(offset + len > req->ns->size)) {
nvmet_req_complete(req, errno_to_nvme_status(req, -ENOSPC));
return;
}
ret = vfs_fallocate(req->ns->file, mode, offset, len);
nvmet_req_complete(req, ret < 0 ? errno_to_nvme_status(req, ret) : 0);
}
static void nvmet_file_execute_write_zeroes(struct nvmet_req *req)
{
if (!nvmet_check_transfer_len(req, 0))
return;
INIT_WORK(&req->f.work, nvmet_file_write_zeroes_work);
queue_work(nvmet_wq, &req->f.work);
}
u16 nvmet_file_parse_io_cmd(struct nvmet_req *req)
{
switch (req->cmd->common.opcode) {
case nvme_cmd_read:
case nvme_cmd_write:
req->execute = nvmet_file_execute_rw;
return 0;
case nvme_cmd_flush:
req->execute = nvmet_file_execute_flush;
return 0;
case nvme_cmd_dsm:
req->execute = nvmet_file_execute_dsm;
return 0;
case nvme_cmd_write_zeroes:
req->execute = nvmet_file_execute_write_zeroes;
return 0;
default:
return nvmet_report_invalid_opcode(req);
}
}
+723
View File
@@ -0,0 +1,723 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe over Fabrics loopback device.
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/scatterlist.h>
#include <linux/blk-mq.h>
#include <linux/nvme.h>
#include <linux/module.h>
#include <linux/parser.h>
#include "nvmet.h"
#include "../host/nvme.h"
#include "../host/fabrics.h"
#define NVME_LOOP_MAX_SEGMENTS 256
struct nvme_loop_iod {
struct nvme_request nvme_req;
struct nvme_command cmd;
struct nvme_completion cqe;
struct nvmet_req req;
struct nvme_loop_queue *queue;
struct work_struct work;
struct sg_table sg_table;
struct scatterlist first_sgl[];
};
struct nvme_loop_ctrl {
struct nvme_loop_queue *queues;
struct blk_mq_tag_set admin_tag_set;
struct list_head list;
struct blk_mq_tag_set tag_set;
struct nvme_ctrl ctrl;
struct nvmet_port *port;
/* Must be last --ends in a flexible-array member. */
struct nvme_loop_iod async_event_iod;
};
static inline struct nvme_loop_ctrl *to_loop_ctrl(struct nvme_ctrl *ctrl)
{
return container_of(ctrl, struct nvme_loop_ctrl, ctrl);
}
enum nvme_loop_queue_flags {
NVME_LOOP_Q_LIVE = 0,
};
struct nvme_loop_queue {
struct nvmet_cq nvme_cq;
struct nvmet_sq nvme_sq;
struct nvme_loop_ctrl *ctrl;
unsigned long flags;
};
static LIST_HEAD(nvme_loop_ports);
static DEFINE_MUTEX(nvme_loop_ports_mutex);
static LIST_HEAD(nvme_loop_ctrl_list);
static DEFINE_MUTEX(nvme_loop_ctrl_mutex);
static void nvme_loop_queue_response(struct nvmet_req *nvme_req);
static void nvme_loop_delete_ctrl(struct nvmet_ctrl *ctrl);
static const struct nvmet_fabrics_ops nvme_loop_ops;
static inline int nvme_loop_queue_idx(struct nvme_loop_queue *queue)
{
return queue - queue->ctrl->queues;
}
static void nvme_loop_complete_rq(struct request *req)
{
struct nvme_loop_iod *iod = blk_mq_rq_to_pdu(req);
sg_free_table_chained(&iod->sg_table, NVME_INLINE_SG_CNT);
nvme_complete_rq(req);
}
static struct blk_mq_tags *nvme_loop_tagset(struct nvme_loop_queue *queue)
{
u32 queue_idx = nvme_loop_queue_idx(queue);
if (queue_idx == 0)
return queue->ctrl->admin_tag_set.tags[queue_idx];
return queue->ctrl->tag_set.tags[queue_idx - 1];
}
static void nvme_loop_queue_response(struct nvmet_req *req)
{
struct nvme_loop_queue *queue =
container_of(req->sq, struct nvme_loop_queue, nvme_sq);
struct nvme_completion *cqe = req->cqe;
/*
* AEN requests are special as they don't time out and can
* survive any kind of queue freeze and often don't respond to
* aborts. We don't even bother to allocate a struct request
* for them but rather special case them here.
*/
if (unlikely(nvme_is_aen_req(nvme_loop_queue_idx(queue),
cqe->command_id))) {
nvme_complete_async_event(&queue->ctrl->ctrl, cqe->status,
&cqe->result);
} else {
struct request *rq;
rq = nvme_find_rq(nvme_loop_tagset(queue), cqe->command_id);
if (!rq) {
dev_err(queue->ctrl->ctrl.device,
"got bad command_id %#x on queue %d\n",
cqe->command_id, nvme_loop_queue_idx(queue));
return;
}
if (!nvme_try_complete_req(rq, cqe->status, cqe->result))
nvme_loop_complete_rq(rq);
}
}
static void nvme_loop_execute_work(struct work_struct *work)
{
struct nvme_loop_iod *iod =
container_of(work, struct nvme_loop_iod, work);
iod->req.execute(&iod->req);
}
static blk_status_t nvme_loop_queue_rq(struct blk_mq_hw_ctx *hctx,
const struct blk_mq_queue_data *bd)
{
struct nvme_ns *ns = hctx->queue->queuedata;
struct nvme_loop_queue *queue = hctx->driver_data;
struct request *req = bd->rq;
struct nvme_loop_iod *iod = blk_mq_rq_to_pdu(req);
bool queue_ready = test_bit(NVME_LOOP_Q_LIVE, &queue->flags);
blk_status_t ret;
if (!nvme_check_ready(&queue->ctrl->ctrl, req, queue_ready))
return nvme_fail_nonready_command(&queue->ctrl->ctrl, req);
ret = nvme_setup_cmd(ns, req);
if (ret)
return ret;
nvme_start_request(req);
iod->cmd.common.flags |= NVME_CMD_SGL_METABUF;
iod->req.port = queue->ctrl->port;
if (!nvmet_req_init(&iod->req, &queue->nvme_sq, &nvme_loop_ops))
return BLK_STS_OK;
if (blk_rq_nr_phys_segments(req)) {
iod->sg_table.sgl = iod->first_sgl;
if (sg_alloc_table_chained(&iod->sg_table,
blk_rq_nr_phys_segments(req),
iod->sg_table.sgl, NVME_INLINE_SG_CNT)) {
nvme_cleanup_cmd(req);
return BLK_STS_RESOURCE;
}
iod->req.sg = iod->sg_table.sgl;
iod->req.sg_cnt = blk_rq_map_sg(req, iod->sg_table.sgl);
iod->req.transfer_len = blk_rq_payload_bytes(req);
}
queue_work(nvmet_wq, &iod->work);
return BLK_STS_OK;
}
static void nvme_loop_submit_async_event(struct nvme_ctrl *arg)
{
struct nvme_loop_ctrl *ctrl = to_loop_ctrl(arg);
struct nvme_loop_queue *queue = &ctrl->queues[0];
struct nvme_loop_iod *iod = &ctrl->async_event_iod;
memset(&iod->cmd, 0, sizeof(iod->cmd));
iod->cmd.common.opcode = nvme_admin_async_event;
iod->cmd.common.command_id = NVME_AQ_BLK_MQ_DEPTH;
iod->cmd.common.flags |= NVME_CMD_SGL_METABUF;
if (!nvmet_req_init(&iod->req, &queue->nvme_sq, &nvme_loop_ops)) {
dev_err(ctrl->ctrl.device, "failed async event work\n");
return;
}
queue_work(nvmet_wq, &iod->work);
}
static int nvme_loop_init_iod(struct nvme_loop_ctrl *ctrl,
struct nvme_loop_iod *iod, unsigned int queue_idx)
{
iod->req.cmd = &iod->cmd;
iod->req.cqe = &iod->cqe;
iod->queue = &ctrl->queues[queue_idx];
INIT_WORK(&iod->work, nvme_loop_execute_work);
return 0;
}
static int nvme_loop_init_request(struct blk_mq_tag_set *set,
struct request *req, unsigned int hctx_idx,
int numa_node)
{
struct nvme_loop_ctrl *ctrl = to_loop_ctrl(set->driver_data);
struct nvme_loop_iod *iod = blk_mq_rq_to_pdu(req);
nvme_req(req)->ctrl = &ctrl->ctrl;
nvme_req(req)->cmd = &iod->cmd;
return nvme_loop_init_iod(ctrl, blk_mq_rq_to_pdu(req),
(set == &ctrl->tag_set) ? hctx_idx + 1 : 0);
}
static struct lock_class_key loop_hctx_fq_lock_key;
static int nvme_loop_init_hctx(struct blk_mq_hw_ctx *hctx, void *data,
unsigned int hctx_idx)
{
struct nvme_loop_ctrl *ctrl = to_loop_ctrl(data);
struct nvme_loop_queue *queue = &ctrl->queues[hctx_idx + 1];
BUG_ON(hctx_idx >= ctrl->ctrl.queue_count);
/*
* flush_end_io() can be called recursively for us, so use our own
* lock class key for avoiding lockdep possible recursive locking,
* then we can remove the dynamically allocated lock class for each
* flush queue, that way may cause horrible boot delay.
*/
blk_mq_hctx_set_fq_lock_class(hctx, &loop_hctx_fq_lock_key);
hctx->driver_data = queue;
return 0;
}
static int nvme_loop_init_admin_hctx(struct blk_mq_hw_ctx *hctx, void *data,
unsigned int hctx_idx)
{
struct nvme_loop_ctrl *ctrl = to_loop_ctrl(data);
struct nvme_loop_queue *queue = &ctrl->queues[0];
BUG_ON(hctx_idx != 0);
hctx->driver_data = queue;
return 0;
}
static const struct blk_mq_ops nvme_loop_mq_ops = {
.queue_rq = nvme_loop_queue_rq,
.complete = nvme_loop_complete_rq,
.init_request = nvme_loop_init_request,
.init_hctx = nvme_loop_init_hctx,
};
static const struct blk_mq_ops nvme_loop_admin_mq_ops = {
.queue_rq = nvme_loop_queue_rq,
.complete = nvme_loop_complete_rq,
.init_request = nvme_loop_init_request,
.init_hctx = nvme_loop_init_admin_hctx,
};
static void nvme_loop_destroy_admin_queue(struct nvme_loop_ctrl *ctrl)
{
if (!test_and_clear_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags))
return;
/*
* It's possible that some requests might have been added
* after admin queue is stopped/quiesced. So now start the
* queue to flush these requests to the completion.
*/
nvme_unquiesce_admin_queue(&ctrl->ctrl);
nvmet_sq_destroy(&ctrl->queues[0].nvme_sq);
nvmet_cq_put(&ctrl->queues[0].nvme_cq);
}
static void nvme_loop_free_ctrl(struct nvme_ctrl *nctrl)
{
struct nvme_loop_ctrl *ctrl = to_loop_ctrl(nctrl);
if (list_empty(&ctrl->list))
goto free_ctrl;
mutex_lock(&nvme_loop_ctrl_mutex);
list_del(&ctrl->list);
mutex_unlock(&nvme_loop_ctrl_mutex);
if (nctrl->tagset)
nvme_remove_io_tag_set(nctrl);
kfree(ctrl->queues);
nvmf_free_options(nctrl->opts);
free_ctrl:
kfree(ctrl);
}
static void nvme_loop_destroy_io_queues(struct nvme_loop_ctrl *ctrl)
{
int i;
for (i = 1; i < ctrl->ctrl.queue_count; i++) {
clear_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[i].flags);
nvmet_sq_destroy(&ctrl->queues[i].nvme_sq);
nvmet_cq_put(&ctrl->queues[i].nvme_cq);
}
ctrl->ctrl.queue_count = 1;
/*
* It's possible that some requests might have been added
* after io queue is stopped/quiesced. So now start the
* queue to flush these requests to the completion.
*/
nvme_unquiesce_io_queues(&ctrl->ctrl);
}
static int nvme_loop_init_io_queues(struct nvme_loop_ctrl *ctrl)
{
struct nvmf_ctrl_options *opts = ctrl->ctrl.opts;
unsigned int nr_io_queues;
int ret, i;
nr_io_queues = min(opts->nr_io_queues, num_online_cpus());
ret = nvme_set_queue_count(&ctrl->ctrl, &nr_io_queues);
if (ret || !nr_io_queues)
return ret;
dev_info(ctrl->ctrl.device, "creating %d I/O queues.\n", nr_io_queues);
for (i = 1; i <= nr_io_queues; i++) {
ctrl->queues[i].ctrl = ctrl;
nvmet_cq_init(&ctrl->queues[i].nvme_cq);
ret = nvmet_sq_init(&ctrl->queues[i].nvme_sq,
&ctrl->queues[i].nvme_cq);
if (ret) {
nvmet_cq_put(&ctrl->queues[i].nvme_cq);
goto out_destroy_queues;
}
ctrl->ctrl.queue_count++;
}
return 0;
out_destroy_queues:
nvme_loop_destroy_io_queues(ctrl);
return ret;
}
static int nvme_loop_connect_io_queues(struct nvme_loop_ctrl *ctrl)
{
int i, ret;
for (i = 1; i < ctrl->ctrl.queue_count; i++) {
ret = nvmf_connect_io_queue(&ctrl->ctrl, i);
if (ret)
return ret;
set_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[i].flags);
}
return 0;
}
static int nvme_loop_configure_admin_queue(struct nvme_loop_ctrl *ctrl)
{
int error;
ctrl->queues[0].ctrl = ctrl;
nvmet_cq_init(&ctrl->queues[0].nvme_cq);
error = nvmet_sq_init(&ctrl->queues[0].nvme_sq,
&ctrl->queues[0].nvme_cq);
if (error) {
nvmet_cq_put(&ctrl->queues[0].nvme_cq);
return error;
}
ctrl->ctrl.queue_count = 1;
/* reset stopped state for the fresh admin queue */
clear_bit(NVME_CTRL_ADMIN_Q_STOPPED, &ctrl->ctrl.flags);
error = nvmf_connect_admin_queue(&ctrl->ctrl);
if (error)
goto out_free_sq;
set_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags);
error = nvme_enable_ctrl(&ctrl->ctrl);
if (error)
goto out_free_sq;
ctrl->ctrl.max_hw_sectors =
(NVME_LOOP_MAX_SEGMENTS - 1) << PAGE_SECTORS_SHIFT;
nvme_unquiesce_admin_queue(&ctrl->ctrl);
error = nvme_init_ctrl_finish(&ctrl->ctrl, false);
if (error)
goto out_free_sq;
return 0;
out_free_sq:
clear_bit(NVME_LOOP_Q_LIVE, &ctrl->queues[0].flags);
nvmet_sq_destroy(&ctrl->queues[0].nvme_sq);
nvmet_cq_put(&ctrl->queues[0].nvme_cq);
return error;
}
static void nvme_loop_shutdown_ctrl(struct nvme_loop_ctrl *ctrl)
{
if (ctrl->ctrl.queue_count > 1) {
nvme_quiesce_io_queues(&ctrl->ctrl);
nvme_loop_destroy_io_queues(ctrl);
}
nvme_quiesce_admin_queue(&ctrl->ctrl);
if (nvme_ctrl_state(&ctrl->ctrl) == NVME_CTRL_LIVE)
nvme_disable_ctrl(&ctrl->ctrl, true);
nvme_loop_destroy_admin_queue(ctrl);
}
static void nvme_loop_delete_ctrl_host(struct nvme_ctrl *ctrl)
{
nvme_loop_shutdown_ctrl(to_loop_ctrl(ctrl));
nvme_remove_admin_tag_set(ctrl);
}
static void nvme_loop_delete_ctrl(struct nvmet_ctrl *nctrl)
{
struct nvme_loop_ctrl *ctrl;
mutex_lock(&nvme_loop_ctrl_mutex);
list_for_each_entry(ctrl, &nvme_loop_ctrl_list, list) {
if (ctrl->ctrl.cntlid == nctrl->cntlid)
nvme_delete_ctrl(&ctrl->ctrl);
}
mutex_unlock(&nvme_loop_ctrl_mutex);
}
static void nvme_loop_reset_ctrl_work(struct work_struct *work)
{
struct nvme_loop_ctrl *ctrl =
container_of(work, struct nvme_loop_ctrl, ctrl.reset_work);
int ret;
nvme_stop_ctrl(&ctrl->ctrl);
nvme_loop_shutdown_ctrl(ctrl);
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING)) {
enum nvme_ctrl_state state = nvme_ctrl_state(&ctrl->ctrl);
if (state != NVME_CTRL_DELETING &&
state != NVME_CTRL_DELETING_NOIO)
/* state change failure for non-deleted ctrl? */
WARN_ON_ONCE(1);
return;
}
ret = nvme_loop_configure_admin_queue(ctrl);
if (ret)
goto out_disable;
ret = nvme_loop_init_io_queues(ctrl);
if (ret)
goto out_destroy_admin;
ret = nvme_loop_connect_io_queues(ctrl);
if (ret)
goto out_destroy_io;
blk_mq_update_nr_hw_queues(&ctrl->tag_set,
ctrl->ctrl.queue_count - 1);
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_LIVE))
WARN_ON_ONCE(1);
nvme_start_ctrl(&ctrl->ctrl);
return;
out_destroy_io:
nvme_loop_destroy_io_queues(ctrl);
out_destroy_admin:
nvme_quiesce_admin_queue(&ctrl->ctrl);
nvme_cancel_admin_tagset(&ctrl->ctrl);
nvme_loop_destroy_admin_queue(ctrl);
out_disable:
nvme_remove_admin_tag_set(&ctrl->ctrl);
dev_warn(ctrl->ctrl.device, "Removing after reset failure\n");
nvme_uninit_ctrl(&ctrl->ctrl);
}
static const struct nvme_ctrl_ops nvme_loop_ctrl_ops = {
.name = "loop",
.module = THIS_MODULE,
.flags = NVME_F_FABRICS,
.reg_read32 = nvmf_reg_read32,
.reg_read64 = nvmf_reg_read64,
.reg_write32 = nvmf_reg_write32,
.free_ctrl = nvme_loop_free_ctrl,
.submit_async_event = nvme_loop_submit_async_event,
.delete_ctrl = nvme_loop_delete_ctrl_host,
.get_address = nvmf_get_address,
.get_virt_boundary = nvme_get_virt_boundary,
};
static int nvme_loop_create_io_queues(struct nvme_loop_ctrl *ctrl)
{
int ret;
ret = nvme_loop_init_io_queues(ctrl);
if (ret)
return ret;
ret = nvme_alloc_io_tag_set(&ctrl->ctrl, &ctrl->tag_set,
&nvme_loop_mq_ops, 1,
sizeof(struct nvme_loop_iod) +
NVME_INLINE_SG_CNT * sizeof(struct scatterlist));
if (ret)
goto out_destroy_queues;
ret = nvme_loop_connect_io_queues(ctrl);
if (ret)
goto out_cleanup_tagset;
return 0;
out_cleanup_tagset:
nvme_remove_io_tag_set(&ctrl->ctrl);
out_destroy_queues:
nvme_loop_destroy_io_queues(ctrl);
return ret;
}
static struct nvmet_port *nvme_loop_find_port(struct nvme_ctrl *ctrl)
{
struct nvmet_port *p, *found = NULL;
mutex_lock(&nvme_loop_ports_mutex);
list_for_each_entry(p, &nvme_loop_ports, entry) {
/* if no transport address is specified use the first port */
if ((ctrl->opts->mask & NVMF_OPT_TRADDR) &&
strcmp(ctrl->opts->traddr, p->disc_addr.traddr))
continue;
found = p;
break;
}
mutex_unlock(&nvme_loop_ports_mutex);
return found;
}
static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev,
struct nvmf_ctrl_options *opts)
{
struct nvme_loop_ctrl *ctrl;
int ret;
ctrl = kzalloc_obj(*ctrl);
if (!ctrl)
return ERR_PTR(-ENOMEM);
ctrl->ctrl.opts = opts;
INIT_LIST_HEAD(&ctrl->list);
INIT_WORK(&ctrl->ctrl.reset_work, nvme_loop_reset_ctrl_work);
ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_loop_ctrl_ops,
0 /* no quirks, we're perfect! */);
if (ret) {
kfree(ctrl);
goto out;
}
ret = nvme_add_ctrl(&ctrl->ctrl);
if (ret)
goto out_put_ctrl;
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING))
WARN_ON_ONCE(1);
ret = -ENOMEM;
ctrl->ctrl.kato = opts->kato;
ctrl->port = nvme_loop_find_port(&ctrl->ctrl);
ctrl->queues = kzalloc_objs(*ctrl->queues, opts->nr_io_queues + 1);
if (!ctrl->queues)
goto out_uninit_ctrl;
ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
&nvme_loop_admin_mq_ops,
sizeof(struct nvme_loop_iod) +
NVME_INLINE_SG_CNT * sizeof(struct scatterlist));
if (ret)
goto out_free_queues;
ret = nvme_loop_configure_admin_queue(ctrl);
if (ret)
goto out_remove_admin_tagset;
if (opts->queue_size > ctrl->ctrl.maxcmd) {
/* warn if maxcmd is lower than queue_size */
dev_warn(ctrl->ctrl.device,
"queue_size %zu > ctrl maxcmd %u, clamping down\n",
opts->queue_size, ctrl->ctrl.maxcmd);
opts->queue_size = ctrl->ctrl.maxcmd;
}
ctrl->ctrl.sqsize = opts->queue_size - 1;
if (opts->nr_io_queues) {
ret = nvme_loop_create_io_queues(ctrl);
if (ret)
goto out_remove_admin_queue;
}
nvme_loop_init_iod(ctrl, &ctrl->async_event_iod, 0);
dev_info(ctrl->ctrl.device,
"new ctrl: \"%s\"\n", ctrl->ctrl.opts->subsysnqn);
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_LIVE))
WARN_ON_ONCE(1);
mutex_lock(&nvme_loop_ctrl_mutex);
list_add_tail(&ctrl->list, &nvme_loop_ctrl_list);
mutex_unlock(&nvme_loop_ctrl_mutex);
nvme_start_ctrl(&ctrl->ctrl);
return &ctrl->ctrl;
out_remove_admin_queue:
nvme_quiesce_admin_queue(&ctrl->ctrl);
nvme_cancel_admin_tagset(&ctrl->ctrl);
nvme_loop_destroy_admin_queue(ctrl);
out_remove_admin_tagset:
nvme_remove_admin_tag_set(&ctrl->ctrl);
out_free_queues:
kfree(ctrl->queues);
out_uninit_ctrl:
nvme_uninit_ctrl(&ctrl->ctrl);
out_put_ctrl:
nvme_put_ctrl(&ctrl->ctrl);
out:
if (ret > 0)
ret = -EIO;
return ERR_PTR(ret);
}
static int nvme_loop_add_port(struct nvmet_port *port)
{
mutex_lock(&nvme_loop_ports_mutex);
list_add_tail(&port->entry, &nvme_loop_ports);
mutex_unlock(&nvme_loop_ports_mutex);
return 0;
}
static void nvme_loop_remove_port(struct nvmet_port *port)
{
mutex_lock(&nvme_loop_ports_mutex);
list_del_init(&port->entry);
mutex_unlock(&nvme_loop_ports_mutex);
/*
* Ensure any ctrls that are in the process of being
* deleted are in fact deleted before we return
* and free the port. This is to prevent active
* ctrls from using a port after it's freed.
*/
flush_workqueue(nvme_delete_wq);
}
static const struct nvmet_fabrics_ops nvme_loop_ops = {
.owner = THIS_MODULE,
.type = NVMF_TRTYPE_LOOP,
.add_port = nvme_loop_add_port,
.remove_port = nvme_loop_remove_port,
.queue_response = nvme_loop_queue_response,
.delete_ctrl = nvme_loop_delete_ctrl,
};
static struct nvmf_transport_ops nvme_loop_transport = {
.name = "loop",
.module = THIS_MODULE,
.create_ctrl = nvme_loop_create_ctrl,
.allowed_opts = NVMF_OPT_TRADDR,
};
static int __init nvme_loop_init_module(void)
{
int ret;
ret = nvmet_register_transport(&nvme_loop_ops);
if (ret)
return ret;
ret = nvmf_register_transport(&nvme_loop_transport);
if (ret)
nvmet_unregister_transport(&nvme_loop_ops);
return ret;
}
static void __exit nvme_loop_cleanup_module(void)
{
struct nvme_loop_ctrl *ctrl, *next;
nvmf_unregister_transport(&nvme_loop_transport);
nvmet_unregister_transport(&nvme_loop_ops);
mutex_lock(&nvme_loop_ctrl_mutex);
list_for_each_entry_safe(ctrl, next, &nvme_loop_ctrl_list, list)
nvme_delete_ctrl(&ctrl->ctrl);
mutex_unlock(&nvme_loop_ctrl_mutex);
flush_workqueue(nvme_delete_wq);
}
module_init(nvme_loop_init_module);
module_exit(nvme_loop_cleanup_module);
MODULE_DESCRIPTION("NVMe target loop transport driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("nvmet-transport-254"); /* 254 == NVMF_TRTYPE_LOOP */
+993
View File
@@ -0,0 +1,993 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
*/
#ifndef _NVMET_H
#define _NVMET_H
#include <linux/dma-mapping.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/kref.h>
#include <linux/percpu-refcount.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/uuid.h>
#include <linux/nvme.h>
#include <linux/configfs.h>
#include <linux/rcupdate.h>
#include <linux/blkdev.h>
#include <linux/radix-tree.h>
#include <linux/t10-pi.h>
#include <linux/kfifo.h>
#define NVMET_DEFAULT_VS NVME_VS(2, 1, 0)
#define NVMET_NS_ENABLED XA_MARK_1
#define NVMET_ASYNC_EVENTS 4
#define NVMET_ERROR_LOG_SLOTS 128
#define NVMET_NO_ERROR_LOC ((u16)-1)
#define NVMET_DEFAULT_CTRL_MODEL "Linux"
#define NVMET_MN_MAX_SIZE 40
#define NVMET_SN_MAX_SIZE 20
#define NVMET_FR_MAX_SIZE 8
#define NVMET_PR_LOG_QUEUE_SIZE 64
#define nvmet_for_each_ns(xa, index, entry) \
xa_for_each(xa, index, entry)
#define nvmet_for_each_enabled_ns(xa, index, entry) \
xa_for_each_marked(xa, index, entry, NVMET_NS_ENABLED)
/*
* Supported optional AENs:
*/
#define NVMET_AEN_CFG_OPTIONAL \
(NVME_AEN_CFG_NS_ATTR | NVME_AEN_CFG_ANA_CHANGE)
#define NVMET_DISC_AEN_CFG_OPTIONAL \
(NVME_AEN_CFG_DISC_CHANGE)
/*
* Plus mandatory SMART AENs (we'll never send them, but allow enabling them):
*/
#define NVMET_AEN_CFG_ALL \
(NVME_SMART_CRIT_SPARE | NVME_SMART_CRIT_TEMPERATURE | \
NVME_SMART_CRIT_RELIABILITY | NVME_SMART_CRIT_MEDIA | \
NVME_SMART_CRIT_VOLATILE_MEMORY | NVMET_AEN_CFG_OPTIONAL)
/* Helper Macros when NVMe error is NVME_SC_CONNECT_INVALID_PARAM
* The 16 bit shift is to set IATTR bit to 1, which means offending
* offset starts in the data section of connect()
*/
#define IPO_IATTR_CONNECT_DATA(x) \
(cpu_to_le32((1 << 16) | (offsetof(struct nvmf_connect_data, x))))
#define IPO_IATTR_CONNECT_SQE(x) \
(cpu_to_le32(offsetof(struct nvmf_connect_command, x)))
struct nvmet_pr_registrant {
u64 rkey;
uuid_t hostid;
enum nvme_pr_type rtype;
struct list_head entry;
struct rcu_head rcu;
};
struct nvmet_pr {
bool enable;
unsigned long notify_mask;
atomic_t generation;
struct nvmet_pr_registrant __rcu *holder;
/*
* During the execution of the reservation command, mutual
* exclusion is required throughout the process. However,
* while waiting asynchronously for the 'per controller
* percpu_ref' to complete before the 'preempt and abort'
* command finishes, a semaphore is needed to ensure mutual
* exclusion instead of a mutex.
*/
struct semaphore pr_sem;
struct list_head registrant_list;
};
struct nvmet_pr_per_ctrl_ref {
struct percpu_ref ref;
struct completion free_done;
struct completion confirm_done;
uuid_t hostid;
};
struct nvmet_ns {
struct percpu_ref ref;
struct file *bdev_file;
struct block_device *bdev;
struct file *file;
bool readonly;
u32 nsid;
u32 blksize_shift;
loff_t size;
u8 nguid[16];
uuid_t uuid;
u32 anagrpid;
bool buffered_io;
bool enabled;
struct nvmet_subsys *subsys;
const char *device_path;
struct config_group device_group;
struct config_group group;
struct completion disable_done;
mempool_t *bvec_pool;
struct pci_dev *p2p_dev;
int use_p2pmem;
int pi_type;
int metadata_size;
u8 csi;
struct nvmet_pr pr;
struct xarray pr_per_ctrl_refs;
};
static inline struct nvmet_ns *to_nvmet_ns(struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_ns, group);
}
static inline struct device *nvmet_ns_dev(struct nvmet_ns *ns)
{
return ns->bdev ? disk_to_dev(ns->bdev->bd_disk) : NULL;
}
struct nvmet_cq {
struct nvmet_ctrl *ctrl;
u16 qid;
u16 size;
refcount_t ref;
};
struct nvmet_sq {
struct nvmet_ctrl *ctrl;
struct percpu_ref ref;
struct nvmet_cq *cq;
u16 qid;
u16 size;
u32 sqhd;
bool sqhd_disabled;
#ifdef CONFIG_NVME_TARGET_AUTH
bool authenticated;
struct delayed_work auth_expired_work;
u16 dhchap_tid;
u8 sc_c;
u8 dhchap_status;
u8 dhchap_step;
u8 *dhchap_c1;
u8 *dhchap_c2;
u32 dhchap_s1;
u32 dhchap_s2;
u8 *dhchap_skey;
int dhchap_skey_len;
#endif
#ifdef CONFIG_NVME_TARGET_TCP_TLS
struct key *tls_key;
#endif
struct completion free_done;
struct completion confirm_done;
};
struct nvmet_ana_group {
struct config_group group;
struct nvmet_port *port;
u32 grpid;
};
static inline struct nvmet_ana_group *to_ana_group(struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_ana_group,
group);
}
/**
* struct nvmet_port - Common structure to keep port
* information for the target.
* @entry: Entry into referrals or transport list.
* @disc_addr: Address information is stored in a format defined
* for a discovery log page entry.
* @group: ConfigFS group for this element's folder.
* @priv: Private data for the transport.
*/
struct nvmet_port {
struct list_head entry;
struct nvmf_disc_rsp_page_entry disc_addr;
struct config_group group;
struct config_group subsys_group;
struct list_head subsystems;
struct config_group referrals_group;
struct list_head referrals;
struct list_head global_entry;
struct config_group ana_groups_group;
struct nvmet_ana_group ana_default_group;
struct key *keyring;
void *priv;
bool enabled;
int inline_data_size;
int max_queue_size;
int mdts;
const struct nvmet_fabrics_ops *tr_ops;
bool pi_enable;
enum nvme_ana_state ana_state[];
};
static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_port,
group);
}
static inline struct nvmet_port *ana_groups_to_port(
struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_port,
ana_groups_group);
}
static inline u8 nvmet_port_disc_addr_treq_secure_channel(struct nvmet_port *port)
{
return (port->disc_addr.treq & NVME_TREQ_SECURE_CHANNEL_MASK);
}
static inline bool nvmet_port_secure_channel_required(struct nvmet_port *port)
{
return nvmet_port_disc_addr_treq_secure_channel(port) == NVMF_TREQ_REQUIRED;
}
struct nvmet_pr_log_mgr {
struct mutex lock;
u64 lost_count;
u64 counter;
DECLARE_KFIFO(log_queue, struct nvme_pr_log, NVMET_PR_LOG_QUEUE_SIZE);
};
struct nvmet_ctrl {
struct nvmet_subsys *subsys;
struct nvmet_sq **sqs;
struct nvmet_cq **cqs;
void *drvdata;
bool reset_tbkas;
struct mutex lock;
u64 cap;
u32 cc;
u32 csts;
uuid_t hostid;
u16 cntlid;
u32 kato;
struct nvmet_port *port;
u32 aen_enabled;
unsigned long aen_masked;
struct nvmet_req *async_event_cmds[NVMET_ASYNC_EVENTS];
unsigned int nr_async_event_cmds;
struct list_head async_events;
struct work_struct async_event_work;
struct list_head subsys_entry;
struct kref ref;
struct delayed_work ka_work;
struct work_struct fatal_err_work;
const struct nvmet_fabrics_ops *ops;
__le32 *changed_ns_list;
u32 nr_changed_ns;
char hostnqn[NVMF_NQN_FIELD_LEN];
struct device *p2p_client;
struct radix_tree_root p2p_ns_map;
#ifdef CONFIG_NVME_TARGET_DEBUGFS
struct dentry *debugfs_dir;
#endif
spinlock_t error_lock;
u64 err_counter;
struct nvme_error_slot slots[NVMET_ERROR_LOG_SLOTS];
bool pi_support;
bool concat;
#ifdef CONFIG_NVME_TARGET_AUTH
struct nvme_dhchap_key *host_key;
struct nvme_dhchap_key *ctrl_key;
u8 shash_id;
struct crypto_kpp *dh_tfm;
u8 dh_gid;
u8 *dh_key;
size_t dh_keysize;
#endif
#ifdef CONFIG_NVME_TARGET_TCP_TLS
struct key *tls_key;
#endif
struct nvmet_pr_log_mgr pr_log_mgr;
};
struct nvmet_subsys {
enum nvme_subsys_type type;
struct mutex lock;
struct kref ref;
struct xarray namespaces;
unsigned int nr_namespaces;
u32 max_nsid;
u16 cntlid_min;
u16 cntlid_max;
struct list_head ctrls;
struct list_head hosts;
bool allow_any_host;
#ifdef CONFIG_NVME_TARGET_DEBUGFS
struct dentry *debugfs_dir;
#endif
u16 max_qid;
u64 ver;
char serial[NVMET_SN_MAX_SIZE];
bool subsys_discovered;
char *subsysnqn;
bool pi_support;
struct config_group group;
struct config_group namespaces_group;
struct config_group allowed_hosts_group;
u16 vendor_id;
u16 subsys_vendor_id;
char *model_number;
u32 ieee_oui;
char *firmware_rev;
#ifdef CONFIG_NVME_TARGET_PASSTHRU
struct nvme_ctrl *passthru_ctrl;
char *passthru_ctrl_path;
struct config_group passthru_group;
unsigned int admin_timeout;
unsigned int io_timeout;
unsigned int clear_ids;
#endif /* CONFIG_NVME_TARGET_PASSTHRU */
#ifdef CONFIG_BLK_DEV_ZONED
u8 zasl;
#endif /* CONFIG_BLK_DEV_ZONED */
};
static inline struct nvmet_subsys *to_subsys(struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_subsys, group);
}
static inline struct nvmet_subsys *namespaces_to_subsys(
struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_subsys,
namespaces_group);
}
struct nvmet_host {
struct config_group group;
u8 *dhchap_secret;
u8 *dhchap_ctrl_secret;
u8 dhchap_key_hash;
u8 dhchap_ctrl_key_hash;
u8 dhchap_hash_id;
u8 dhchap_dhgroup_id;
};
static inline struct nvmet_host *to_host(struct config_item *item)
{
return container_of(to_config_group(item), struct nvmet_host, group);
}
static inline char *nvmet_host_name(struct nvmet_host *host)
{
return config_item_name(&host->group.cg_item);
}
struct nvmet_host_link {
struct list_head entry;
struct nvmet_host *host;
};
struct nvmet_subsys_link {
struct list_head entry;
struct nvmet_subsys *subsys;
};
struct nvmet_req;
struct nvmet_fabrics_ops {
struct module *owner;
unsigned int type;
unsigned int msdbd;
unsigned int flags;
#define NVMF_KEYED_SGLS (1 << 0)
#define NVMF_METADATA_SUPPORTED (1 << 1)
void (*queue_response)(struct nvmet_req *req);
int (*add_port)(struct nvmet_port *port);
void (*remove_port)(struct nvmet_port *port);
void (*delete_ctrl)(struct nvmet_ctrl *ctrl);
void (*disc_traddr)(struct nvmet_req *req,
struct nvmet_port *port, char *traddr);
ssize_t (*host_traddr)(struct nvmet_ctrl *ctrl,
char *traddr, size_t traddr_len);
u16 (*install_queue)(struct nvmet_sq *nvme_sq);
void (*discovery_chg)(struct nvmet_port *port);
u8 (*get_mdts)(const struct nvmet_ctrl *ctrl);
u16 (*get_max_queue_size)(const struct nvmet_ctrl *ctrl);
/* Operations mandatory for PCI target controllers */
u16 (*create_sq)(struct nvmet_ctrl *ctrl, u16 sqid, u16 cqid, u16 flags,
u16 qsize, u64 prp1);
u16 (*delete_sq)(struct nvmet_ctrl *ctrl, u16 sqid);
u16 (*create_cq)(struct nvmet_ctrl *ctrl, u16 cqid, u16 flags,
u16 qsize, u64 prp1, u16 irq_vector);
u16 (*delete_cq)(struct nvmet_ctrl *ctrl, u16 cqid);
u16 (*set_feature)(const struct nvmet_ctrl *ctrl, u8 feat,
void *feat_data);
u16 (*get_feature)(const struct nvmet_ctrl *ctrl, u8 feat,
void *feat_data);
};
#define NVMET_MAX_INLINE_BIOVEC 8
#define NVMET_MAX_INLINE_DATA_LEN NVMET_MAX_INLINE_BIOVEC * PAGE_SIZE
struct nvmet_req {
struct nvme_command *cmd;
struct nvme_completion *cqe;
struct nvmet_sq *sq;
struct nvmet_cq *cq;
struct nvmet_ns *ns;
struct scatterlist *sg;
struct scatterlist *metadata_sg;
struct bio_vec inline_bvec[NVMET_MAX_INLINE_BIOVEC];
union {
struct {
struct bio inline_bio;
} b;
struct {
bool mpool_alloc;
struct kiocb iocb;
struct bio_vec *bvec;
struct work_struct work;
} f;
struct {
struct bio inline_bio;
struct request *rq;
struct work_struct work;
bool use_workqueue;
} p;
#ifdef CONFIG_BLK_DEV_ZONED
struct {
struct bio inline_bio;
struct work_struct zmgmt_work;
} z;
#endif /* CONFIG_BLK_DEV_ZONED */
struct {
struct work_struct abort_work;
} r;
};
int sg_cnt;
int metadata_sg_cnt;
/* data length as parsed from the SGL descriptor: */
size_t transfer_len;
size_t metadata_len;
struct nvmet_port *port;
void (*execute)(struct nvmet_req *req);
const struct nvmet_fabrics_ops *ops;
struct pci_dev *p2p_dev;
struct device *p2p_client;
u16 error_loc;
u64 error_slba;
struct nvmet_pr_per_ctrl_ref *pc_ref;
};
#define NVMET_MAX_MPOOL_BVEC 16
extern struct kmem_cache *nvmet_bvec_cache;
extern struct workqueue_struct *buffered_io_wq;
extern struct workqueue_struct *zbd_wq;
extern struct workqueue_struct *nvmet_wq;
extern struct workqueue_struct *nvmet_aen_wq;
static inline void nvmet_set_result(struct nvmet_req *req, u32 result)
{
req->cqe->result.u32 = cpu_to_le32(result);
}
/*
* NVMe command writes actually are DMA reads for us on the target side.
*/
static inline enum dma_data_direction
nvmet_data_dir(struct nvmet_req *req)
{
return nvme_is_write(req->cmd) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
}
struct nvmet_async_event {
struct list_head entry;
u8 event_type;
u8 event_info;
u8 log_page;
};
static inline void nvmet_clear_aen_bit(struct nvmet_req *req, u32 bn)
{
int rae = le32_to_cpu(req->cmd->common.cdw10) & 1 << 15;
if (!rae)
clear_bit(bn, &req->sq->ctrl->aen_masked);
}
static inline bool nvmet_aen_bit_disabled(struct nvmet_ctrl *ctrl, u32 bn)
{
if (!(READ_ONCE(ctrl->aen_enabled) & (1 << bn)))
return true;
return test_and_set_bit(bn, &ctrl->aen_masked);
}
void nvmet_get_feat_kato(struct nvmet_req *req);
void nvmet_get_feat_async_event(struct nvmet_req *req);
u16 nvmet_set_feat_kato(struct nvmet_req *req);
u16 nvmet_set_feat_async_event(struct nvmet_req *req, u32 mask);
void nvmet_execute_async_event(struct nvmet_req *req);
void nvmet_start_keep_alive_timer(struct nvmet_ctrl *ctrl);
void nvmet_stop_keep_alive_timer(struct nvmet_ctrl *ctrl);
u16 nvmet_parse_connect_cmd(struct nvmet_req *req);
u32 nvmet_connect_cmd_data_len(struct nvmet_req *req);
void nvmet_bdev_set_limits(struct block_device *bdev, struct nvme_id_ns *id);
void nvmet_bdev_set_nvm_limits(struct block_device *bdev,
struct nvme_id_ns_nvm *id);
u16 nvmet_bdev_parse_io_cmd(struct nvmet_req *req);
u16 nvmet_file_parse_io_cmd(struct nvmet_req *req);
u16 nvmet_bdev_zns_parse_io_cmd(struct nvmet_req *req);
u32 nvmet_admin_cmd_data_len(struct nvmet_req *req);
u16 nvmet_parse_admin_cmd(struct nvmet_req *req);
u32 nvmet_discovery_cmd_data_len(struct nvmet_req *req);
u16 nvmet_parse_discovery_cmd(struct nvmet_req *req);
u16 nvmet_parse_fabrics_admin_cmd(struct nvmet_req *req);
u32 nvmet_fabrics_admin_cmd_data_len(struct nvmet_req *req);
u16 nvmet_parse_fabrics_io_cmd(struct nvmet_req *req);
u32 nvmet_fabrics_io_cmd_data_len(struct nvmet_req *req);
bool nvmet_req_init(struct nvmet_req *req, struct nvmet_sq *sq,
const struct nvmet_fabrics_ops *ops);
void nvmet_req_uninit(struct nvmet_req *req);
size_t nvmet_req_transfer_len(struct nvmet_req *req);
bool nvmet_check_transfer_len(struct nvmet_req *req, size_t len);
bool nvmet_check_data_len_lte(struct nvmet_req *req, size_t data_len);
void nvmet_req_complete(struct nvmet_req *req, u16 status);
int nvmet_req_alloc_sgls(struct nvmet_req *req);
void nvmet_req_free_sgls(struct nvmet_req *req);
void nvmet_execute_set_features(struct nvmet_req *req);
void nvmet_execute_get_features(struct nvmet_req *req);
void nvmet_execute_keep_alive(struct nvmet_req *req);
u16 nvmet_check_cqid(struct nvmet_ctrl *ctrl, u16 cqid, bool create);
u16 nvmet_check_io_cqid(struct nvmet_ctrl *ctrl, u16 cqid, bool create);
void nvmet_cq_init(struct nvmet_cq *cq);
void nvmet_cq_setup(struct nvmet_ctrl *ctrl, struct nvmet_cq *cq, u16 qid,
u16 size);
u16 nvmet_cq_create(struct nvmet_ctrl *ctrl, struct nvmet_cq *cq, u16 qid,
u16 size);
void nvmet_cq_destroy(struct nvmet_cq *cq);
bool nvmet_cq_get(struct nvmet_cq *cq);
void nvmet_cq_put(struct nvmet_cq *cq);
bool nvmet_cq_in_use(struct nvmet_cq *cq);
u16 nvmet_check_sqid(struct nvmet_ctrl *ctrl, u16 sqid, bool create);
void nvmet_sq_setup(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq, u16 qid,
u16 size);
u16 nvmet_sq_create(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq,
struct nvmet_cq *cq, u16 qid, u16 size);
void nvmet_sq_destroy(struct nvmet_sq *sq);
int nvmet_sq_init(struct nvmet_sq *sq, struct nvmet_cq *cq);
void nvmet_ctrl_fatal_error(struct nvmet_ctrl *ctrl);
void nvmet_update_cc(struct nvmet_ctrl *ctrl, u32 new);
struct nvmet_alloc_ctrl_args {
struct nvmet_port *port;
struct nvmet_sq *sq;
char *subsysnqn;
char *hostnqn;
uuid_t *hostid;
const struct nvmet_fabrics_ops *ops;
struct device *p2p_client;
u32 kato;
__le32 result;
u16 error_loc;
u16 status;
};
struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args);
struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
const char *hostnqn, u16 cntlid,
struct nvmet_req *req);
void nvmet_ctrl_put(struct nvmet_ctrl *ctrl);
u16 nvmet_check_ctrl_status(struct nvmet_req *req);
ssize_t nvmet_ctrl_host_traddr(struct nvmet_ctrl *ctrl,
char *traddr, size_t traddr_len);
struct nvmet_subsys *nvmet_subsys_alloc(const char *subsysnqn,
enum nvme_subsys_type type);
void nvmet_subsys_put(struct nvmet_subsys *subsys);
void nvmet_subsys_del_ctrls(struct nvmet_subsys *subsys);
u16 nvmet_req_find_ns(struct nvmet_req *req);
void nvmet_put_namespace(struct nvmet_ns *ns);
int nvmet_ns_enable(struct nvmet_ns *ns);
void nvmet_ns_disable(struct nvmet_ns *ns);
struct nvmet_ns *nvmet_ns_alloc(struct nvmet_subsys *subsys, u32 nsid);
void nvmet_ns_free(struct nvmet_ns *ns);
void nvmet_send_ana_event(struct nvmet_subsys *subsys,
struct nvmet_port *port);
void nvmet_port_send_ana_event(struct nvmet_port *port);
int nvmet_register_transport(const struct nvmet_fabrics_ops *ops);
void nvmet_unregister_transport(const struct nvmet_fabrics_ops *ops);
void nvmet_port_del_ctrls(struct nvmet_port *port,
struct nvmet_subsys *subsys);
int nvmet_enable_port(struct nvmet_port *port);
void nvmet_disable_port(struct nvmet_port *port);
void nvmet_referral_enable(struct nvmet_port *parent, struct nvmet_port *port);
void nvmet_referral_disable(struct nvmet_port *parent, struct nvmet_port *port);
u16 nvmet_copy_to_sgl(struct nvmet_req *req, off_t off, const void *buf,
size_t len);
u16 nvmet_copy_from_sgl(struct nvmet_req *req, off_t off, void *buf,
size_t len);
u16 nvmet_zero_sgl(struct nvmet_req *req, off_t off, size_t len);
u32 nvmet_get_log_page_len(struct nvme_command *cmd);
u64 nvmet_get_log_page_offset(struct nvme_command *cmd);
extern struct list_head *nvmet_ports;
void nvmet_port_disc_changed(struct nvmet_port *port,
struct nvmet_subsys *subsys);
void nvmet_subsys_disc_changed(struct nvmet_subsys *subsys,
struct nvmet_host *host);
void nvmet_add_async_event(struct nvmet_ctrl *ctrl, u8 event_type,
u8 event_info, u8 log_page);
#define NVMET_MIN_QUEUE_SIZE 16
#define NVMET_MAX_QUEUE_SIZE 1024
#define NVMET_NR_QUEUES 128
#define NVMET_MAX_CMD(ctrl) (NVME_CAP_MQES(ctrl->cap) + 1)
#define NVMET_MAX_MDTS 255
/*
* Nice round number that makes a list of nsids fit into a page.
* Should become tunable at some point in the future.
*/
#define NVMET_MAX_NAMESPACES 1024
/*
* 0 is not a valid ANA group ID, so we start numbering at 1.
*
* ANA Group 1 exists without manual intervention, has namespaces assigned to it
* by default, and is available in an optimized state through all ports.
*/
#define NVMET_MAX_ANAGRPS 128
#define NVMET_DEFAULT_ANA_GRPID 1
#define NVMET_KAS 10
#define NVMET_DISC_KATO_MS 120000
int __init nvmet_init_configfs(void);
void __exit nvmet_exit_configfs(void);
int __init nvmet_init_discovery(void);
void nvmet_exit_discovery(void);
extern struct nvmet_subsys *nvmet_disc_subsys;
extern struct rw_semaphore nvmet_config_sem;
extern u32 nvmet_ana_group_enabled[NVMET_MAX_ANAGRPS + 1];
extern u64 nvmet_ana_chgcnt;
extern struct rw_semaphore nvmet_ana_sem;
bool nvmet_host_allowed(struct nvmet_subsys *subsys, const char *hostnqn);
int nvmet_bdev_ns_enable(struct nvmet_ns *ns);
int nvmet_file_ns_enable(struct nvmet_ns *ns);
void nvmet_bdev_ns_disable(struct nvmet_ns *ns);
void nvmet_file_ns_disable(struct nvmet_ns *ns);
u16 nvmet_bdev_flush(struct nvmet_req *req);
u16 nvmet_file_flush(struct nvmet_req *req);
void nvmet_ns_changed(struct nvmet_subsys *subsys, u32 nsid);
void nvmet_bdev_ns_revalidate(struct nvmet_ns *ns);
void nvmet_file_ns_revalidate(struct nvmet_ns *ns);
bool nvmet_ns_revalidate(struct nvmet_ns *ns);
u16 blk_to_nvme_status(struct nvmet_req *req, blk_status_t blk_sts);
bool nvmet_bdev_zns_enable(struct nvmet_ns *ns);
void nvmet_execute_identify_ctrl_zns(struct nvmet_req *req);
void nvmet_execute_identify_ns_zns(struct nvmet_req *req);
void nvmet_bdev_execute_zone_mgmt_recv(struct nvmet_req *req);
void nvmet_bdev_execute_zone_mgmt_send(struct nvmet_req *req);
void nvmet_bdev_execute_zone_append(struct nvmet_req *req);
static inline u32 nvmet_rw_data_len(struct nvmet_req *req)
{
return ((u32)le16_to_cpu(req->cmd->rw.length) + 1) <<
req->ns->blksize_shift;
}
static inline u32 nvmet_rw_metadata_len(struct nvmet_req *req)
{
if (!IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY))
return 0;
return ((u32)le16_to_cpu(req->cmd->rw.length) + 1) *
req->ns->metadata_size;
}
static inline u32 nvmet_dsm_len(struct nvmet_req *req)
{
return (le32_to_cpu(req->cmd->dsm.nr) + 1) *
sizeof(struct nvme_dsm_range);
}
static inline struct nvmet_subsys *nvmet_req_subsys(struct nvmet_req *req)
{
return req->sq->ctrl->subsys;
}
static inline bool nvmet_is_disc_subsys(struct nvmet_subsys *subsys)
{
return subsys->type != NVME_NQN_NVME;
}
static inline bool nvmet_is_pci_ctrl(struct nvmet_ctrl *ctrl)
{
return ctrl->port->disc_addr.trtype == NVMF_TRTYPE_PCI;
}
/* Limit MDTS according to port config or transport capability */
static inline u8 nvmet_ctrl_mdts(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u8 mdts = req->port->mdts;
if (!ctrl->ops->get_mdts)
return mdts;
return min_not_zero(ctrl->ops->get_mdts(ctrl), mdts);
}
#ifdef CONFIG_NVME_TARGET_PASSTHRU
void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys);
int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys);
void nvmet_passthru_ctrl_disable(struct nvmet_subsys *subsys);
u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req);
u16 nvmet_parse_passthru_io_cmd(struct nvmet_req *req);
static inline bool nvmet_is_passthru_subsys(struct nvmet_subsys *subsys)
{
return subsys->passthru_ctrl;
}
#else /* CONFIG_NVME_TARGET_PASSTHRU */
static inline void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys)
{
}
static inline void nvmet_passthru_ctrl_disable(struct nvmet_subsys *subsys)
{
}
static inline u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req)
{
return 0;
}
static inline u16 nvmet_parse_passthru_io_cmd(struct nvmet_req *req)
{
return 0;
}
static inline bool nvmet_is_passthru_subsys(struct nvmet_subsys *subsys)
{
return NULL;
}
#endif /* CONFIG_NVME_TARGET_PASSTHRU */
static inline bool nvmet_is_passthru_req(struct nvmet_req *req)
{
return nvmet_is_passthru_subsys(nvmet_req_subsys(req));
}
void nvmet_passthrough_override_cap(struct nvmet_ctrl *ctrl);
u16 errno_to_nvme_status(struct nvmet_req *req, int errno);
u16 nvmet_report_invalid_opcode(struct nvmet_req *req);
static inline bool nvmet_cc_en(u32 cc)
{
return (cc & NVME_CC_ENABLE) >> NVME_CC_EN_SHIFT;
}
static inline u8 nvmet_cc_css(u32 cc)
{
return (cc & NVME_CC_CSS_MASK) >> NVME_CC_CSS_SHIFT;
}
static inline u8 nvmet_cc_mps(u32 cc)
{
return (cc & NVME_CC_MPS_MASK) >> NVME_CC_MPS_SHIFT;
}
static inline u8 nvmet_cc_ams(u32 cc)
{
return (cc & NVME_CC_AMS_MASK) >> NVME_CC_AMS_SHIFT;
}
static inline u8 nvmet_cc_shn(u32 cc)
{
return (cc & NVME_CC_SHN_MASK) >> NVME_CC_SHN_SHIFT;
}
static inline u8 nvmet_cc_iosqes(u32 cc)
{
return (cc & NVME_CC_IOSQES_MASK) >> NVME_CC_IOSQES_SHIFT;
}
static inline u8 nvmet_cc_iocqes(u32 cc)
{
return (cc & NVME_CC_IOCQES_MASK) >> NVME_CC_IOCQES_SHIFT;
}
/* Convert a 32-bit number to a 16-bit 0's based number */
static inline __le16 to0based(u32 a)
{
return cpu_to_le16(clamp(a, 1U, 1U << 16) - 1);
}
static inline bool nvmet_ns_has_pi(struct nvmet_ns *ns)
{
if (!IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY))
return false;
return ns->pi_type && ns->metadata_size == sizeof(struct t10_pi_tuple);
}
static inline __le64 nvmet_sect_to_lba(struct nvmet_ns *ns, sector_t sect)
{
return cpu_to_le64(sect >> (ns->blksize_shift - SECTOR_SHIFT));
}
static inline sector_t nvmet_lba_to_sect(struct nvmet_ns *ns, __le64 lba)
{
return le64_to_cpu(lba) << (ns->blksize_shift - SECTOR_SHIFT);
}
static inline bool nvmet_use_inline_bvec(struct nvmet_req *req)
{
return req->transfer_len <= NVMET_MAX_INLINE_DATA_LEN &&
req->sg_cnt <= NVMET_MAX_INLINE_BIOVEC;
}
static inline void nvmet_req_bio_put(struct nvmet_req *req, struct bio *bio)
{
if (bio != &req->b.inline_bio)
bio_put(bio);
else
bio_uninit(bio);
}
#ifdef CONFIG_NVME_TARGET_TCP_TLS
static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq)
{
return sq->tls_key ? key_serial(sq->tls_key) : 0;
}
static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq)
{
if (sq->tls_key) {
key_put(sq->tls_key);
sq->tls_key = NULL;
}
}
#else
static inline key_serial_t nvmet_queue_tls_keyid(struct nvmet_sq *sq) { return 0; }
static inline void nvmet_sq_put_tls_key(struct nvmet_sq *sq) {}
#endif
#ifdef CONFIG_NVME_TARGET_AUTH
u32 nvmet_auth_send_data_len(struct nvmet_req *req);
void nvmet_execute_auth_send(struct nvmet_req *req);
u32 nvmet_auth_receive_data_len(struct nvmet_req *req);
void nvmet_execute_auth_receive(struct nvmet_req *req);
int nvmet_auth_set_key(struct nvmet_host *host, const char *secret,
bool set_ctrl);
int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash);
u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq, bool reset);
void nvmet_auth_sq_init(struct nvmet_sq *sq);
void nvmet_destroy_auth(struct nvmet_ctrl *ctrl);
void nvmet_auth_sq_free(struct nvmet_sq *sq);
int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id);
bool nvmet_check_auth_status(struct nvmet_req *req);
int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response,
unsigned int hash_len);
int nvmet_auth_ctrl_hash(struct nvmet_req *req, u8 *response,
unsigned int hash_len);
static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq)
{
return ctrl->host_key != NULL && !nvmet_queue_tls_keyid(sq);
}
int nvmet_auth_ctrl_exponential(struct nvmet_req *req,
u8 *buf, int buf_size);
int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
const u8 *pkey, int pkey_size);
void nvmet_auth_insert_psk(struct nvmet_sq *sq);
#else
static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl,
struct nvmet_sq *sq, bool reset)
{
return 0;
}
static inline void nvmet_auth_sq_init(struct nvmet_sq *sq)
{
}
static inline void nvmet_destroy_auth(struct nvmet_ctrl *ctrl) {};
static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {};
static inline bool nvmet_check_auth_status(struct nvmet_req *req)
{
return true;
}
static inline bool nvmet_has_auth(struct nvmet_ctrl *ctrl,
struct nvmet_sq *sq)
{
return false;
}
static inline const char *nvmet_dhchap_dhgroup_name(u8 dhgid) { return NULL; }
static inline void nvmet_auth_insert_psk(struct nvmet_sq *sq) {};
#endif
int nvmet_pr_init_ns(struct nvmet_ns *ns);
u16 nvmet_parse_pr_cmd(struct nvmet_req *req);
u16 nvmet_pr_check_cmd_access(struct nvmet_req *req);
int nvmet_ctrl_init_pr(struct nvmet_ctrl *ctrl);
void nvmet_ctrl_destroy_pr(struct nvmet_ctrl *ctrl);
void nvmet_pr_exit_ns(struct nvmet_ns *ns);
void nvmet_execute_get_log_page_resv(struct nvmet_req *req);
u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask);
u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req);
u16 nvmet_pr_get_ns_pc_ref(struct nvmet_req *req);
static inline void nvmet_pr_put_ns_pc_ref(struct nvmet_pr_per_ctrl_ref *pc_ref)
{
percpu_ref_put(&pc_ref->ref);
}
/*
* Data for the get_feature() and set_feature() operations of PCI target
* controllers.
*/
struct nvmet_feat_irq_coalesce {
u8 thr;
u8 time;
};
struct nvmet_feat_irq_config {
u16 iv;
bool cd;
};
struct nvmet_feat_arbitration {
u8 hpw;
u8 mpw;
u8 lpw;
u8 ab;
};
#endif /* _NVMET_H */
+664
View File
@@ -0,0 +1,664 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe Over Fabrics Target Passthrough command implementation.
*
* Copyright (c) 2017-2018 Western Digital Corporation or its
* affiliates.
* Copyright (c) 2019-2020, Eideticom Inc.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include "../host/nvme.h"
#include "nvmet.h"
MODULE_IMPORT_NS("NVME_TARGET_PASSTHRU");
/*
* xarray to maintain one passthru subsystem per nvme controller.
*/
static DEFINE_XARRAY(passthru_subsystems);
void nvmet_passthrough_override_cap(struct nvmet_ctrl *ctrl)
{
/*
* Multiple command set support can only be declared if the underlying
* controller actually supports it.
*/
if (!nvme_multi_css(ctrl->subsys->passthru_ctrl))
ctrl->cap &= ~(1ULL << 43);
}
static u16 nvmet_passthru_override_id_descs(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
u16 status = NVME_SC_SUCCESS;
int pos, len;
bool csi_seen = false;
void *data;
u8 csi;
if (!ctrl->subsys->clear_ids)
return status;
data = kzalloc(NVME_IDENTIFY_DATA_SIZE, GFP_KERNEL);
if (!data)
return NVME_SC_INTERNAL;
status = nvmet_copy_from_sgl(req, 0, data, NVME_IDENTIFY_DATA_SIZE);
if (status)
goto out_free;
for (pos = 0; pos < NVME_IDENTIFY_DATA_SIZE; pos += len) {
struct nvme_ns_id_desc *cur = data + pos;
if (cur->nidl == 0)
break;
if (cur->nidt == NVME_NIDT_CSI) {
memcpy(&csi, cur + 1, NVME_NIDT_CSI_LEN);
csi_seen = true;
break;
}
len = sizeof(struct nvme_ns_id_desc) + cur->nidl;
}
memset(data, 0, NVME_IDENTIFY_DATA_SIZE);
if (csi_seen) {
struct nvme_ns_id_desc *cur = data;
cur->nidt = NVME_NIDT_CSI;
cur->nidl = NVME_NIDT_CSI_LEN;
memcpy(cur + 1, &csi, NVME_NIDT_CSI_LEN);
}
status = nvmet_copy_to_sgl(req, 0, data, NVME_IDENTIFY_DATA_SIZE);
out_free:
kfree(data);
return status;
}
static u16 nvmet_passthru_override_id_ctrl(struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = req->sq->ctrl;
struct nvme_ctrl *pctrl = ctrl->subsys->passthru_ctrl;
u16 status = NVME_SC_SUCCESS;
struct nvme_id_ctrl *id;
unsigned int max_hw_sectors;
int page_shift;
id = kzalloc_obj(*id);
if (!id)
return NVME_SC_INTERNAL;
status = nvmet_copy_from_sgl(req, 0, id, sizeof(*id));
if (status)
goto out_free;
id->cntlid = cpu_to_le16(ctrl->cntlid);
id->ver = cpu_to_le32(ctrl->subsys->ver);
/*
* The passthru NVMe driver may have a limit on the number of segments
* which depends on the host's memory fragmentation. To solve this,
* ensure mdts is limited to the pages equal to the number of segments.
*/
max_hw_sectors = min_not_zero(pctrl->max_segments << PAGE_SECTORS_SHIFT,
pctrl->max_hw_sectors);
/*
* nvmet_passthru_map_sg is limited to using a single bio so limit
* the mdts based on BIO_MAX_VECS as well
*/
max_hw_sectors = min_not_zero(BIO_MAX_VECS << PAGE_SECTORS_SHIFT,
max_hw_sectors);
page_shift = NVME_CAP_MPSMIN(ctrl->cap) + 12;
id->mdts = ilog2(max_hw_sectors) + 9 - page_shift;
id->acl = 3;
/*
* We export aerl limit for the fabrics controller, update this when
* passthru based aerl support is added.
*/
id->aerl = NVMET_ASYNC_EVENTS - 1;
/* emulate kas as most of the PCIe ctrl don't have a support for kas */
id->kas = cpu_to_le16(NVMET_KAS);
/* don't support host memory buffer */
id->hmpre = 0;
id->hmmin = 0;
id->sqes = min_t(__u8, ((0x6 << 4) | 0x6), id->sqes);
id->cqes = min_t(__u8, ((0x4 << 4) | 0x4), id->cqes);
id->maxcmd = cpu_to_le16(NVMET_MAX_CMD(ctrl));
/* don't support fuse commands */
id->fuses = 0;
id->sgls = cpu_to_le32(1 << 0); /* we always support SGLs */
if (ctrl->ops->flags & NVMF_KEYED_SGLS)
id->sgls |= cpu_to_le32(1 << 2);
if (req->port->inline_data_size)
id->sgls |= cpu_to_le32(1 << 20);
/*
* When passthru controller is setup using nvme-loop transport it will
* export the passthru ctrl subsysnqn (PCIe NVMe ctrl) and will fail in
* the nvme/host/core.c in the nvme_init_subsystem()->nvme_active_ctrl()
* code path with duplicate ctrl subsysnqn. In order to prevent that we
* mask the passthru-ctrl subsysnqn with the target ctrl subsysnqn.
*/
strscpy(id->subnqn, ctrl->subsys->subsysnqn, sizeof(id->subnqn));
/* use fabric id-ctrl values */
id->ioccsz = cpu_to_le32((sizeof(struct nvme_command) +
req->port->inline_data_size) / 16);
id->iorcsz = cpu_to_le32(sizeof(struct nvme_completion) / 16);
id->msdbd = ctrl->ops->msdbd;
/* Support multipath connections with fabrics */
id->cmic |= 1 << 1;
/* Disable reservations, see nvmet_parse_passthru_io_cmd() */
id->oncs &= cpu_to_le16(~NVME_CTRL_ONCS_RESERVATIONS);
status = nvmet_copy_to_sgl(req, 0, id, sizeof(struct nvme_id_ctrl));
out_free:
kfree(id);
return status;
}
static u16 nvmet_passthru_override_id_ns(struct nvmet_req *req)
{
u16 status = NVME_SC_SUCCESS;
struct nvme_id_ns *id;
int i;
id = kzalloc_obj(*id);
if (!id)
return NVME_SC_INTERNAL;
status = nvmet_copy_from_sgl(req, 0, id, sizeof(struct nvme_id_ns));
if (status)
goto out_free;
for (i = 0; i < (id->nlbaf + 1); i++)
if (id->lbaf[i].ms)
memset(&id->lbaf[i], 0, sizeof(id->lbaf[i]));
id->flbas = id->flbas & ~(1 << 4);
/*
* Presently the NVMEof target code does not support sending
* metadata, so we must disable it here. This should be updated
* once target starts supporting metadata.
*/
id->mc = 0;
if (req->sq->ctrl->subsys->clear_ids) {
memset(id->nguid, 0, NVME_NIDT_NGUID_LEN);
memset(id->eui64, 0, NVME_NIDT_EUI64_LEN);
}
status = nvmet_copy_to_sgl(req, 0, id, sizeof(*id));
out_free:
kfree(id);
return status;
}
static void nvmet_passthru_execute_cmd_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, p.work);
struct request *rq = req->p.rq;
struct nvme_ctrl *ctrl = nvme_req(rq)->ctrl;
struct nvme_ns *ns = rq->q->queuedata;
u32 effects;
int status;
effects = nvme_passthru_start(ctrl, ns, req->cmd->common.opcode);
status = nvme_execute_rq(rq, false);
if (status == NVME_SC_SUCCESS &&
req->cmd->common.opcode == nvme_admin_identify) {
switch (req->cmd->identify.cns) {
case NVME_ID_CNS_CTRL:
status = nvmet_passthru_override_id_ctrl(req);
break;
case NVME_ID_CNS_NS:
status = nvmet_passthru_override_id_ns(req);
break;
case NVME_ID_CNS_NS_DESC_LIST:
status = nvmet_passthru_override_id_descs(req);
break;
}
} else if (status < 0)
status = NVME_SC_INTERNAL;
req->cqe->result = nvme_req(rq)->result;
nvmet_req_complete(req, status);
blk_mq_free_request(rq);
if (effects)
nvme_passthru_end(ctrl, ns, effects, req->cmd, status);
}
static enum rq_end_io_ret nvmet_passthru_req_done(struct request *rq,
blk_status_t blk_status,
const struct io_comp_batch *iob)
{
struct nvmet_req *req = rq->end_io_data;
req->cqe->result = nvme_req(rq)->result;
nvmet_req_complete(req, nvme_req(rq)->status);
blk_mq_free_request(rq);
return RQ_END_IO_NONE;
}
static int nvmet_passthru_map_sg(struct nvmet_req *req, struct request *rq)
{
struct scatterlist *sg;
struct bio *bio;
int ret = -EINVAL;
int i;
if (req->sg_cnt > BIO_MAX_VECS)
return -EINVAL;
if (nvmet_use_inline_bvec(req)) {
bio = &req->p.inline_bio;
bio_init(bio, NULL, req->inline_bvec,
ARRAY_SIZE(req->inline_bvec), req_op(rq));
} else {
bio = bio_alloc(NULL, bio_max_segs(req->sg_cnt), req_op(rq),
GFP_KERNEL);
bio->bi_end_io = bio_put;
}
for_each_sg(req->sg, sg, req->sg_cnt, i) {
if (bio_add_page(bio, sg_page(sg), sg->length, sg->offset) <
sg->length)
goto out_bio_put;
}
ret = blk_rq_append_bio(rq, bio);
if (ret)
goto out_bio_put;
return 0;
out_bio_put:
nvmet_req_bio_put(req, bio);
return ret;
}
static void nvmet_passthru_execute_cmd(struct nvmet_req *req)
{
struct nvme_ctrl *ctrl = nvmet_req_subsys(req)->passthru_ctrl;
struct request_queue *q = ctrl->admin_q;
struct nvme_ns *ns = NULL;
struct request *rq = NULL;
unsigned int timeout;
u32 effects;
u16 status;
int ret;
if (likely(req->sq->qid != 0)) {
u32 nsid = le32_to_cpu(req->cmd->common.nsid);
ns = nvme_find_get_ns(ctrl, nsid);
if (unlikely(!ns)) {
pr_err("failed to get passthru ns nsid:%u\n", nsid);
status = NVME_SC_INVALID_NS | NVME_STATUS_DNR;
goto out;
}
q = ns->queue;
timeout = nvmet_req_subsys(req)->io_timeout;
} else {
timeout = nvmet_req_subsys(req)->admin_timeout;
}
rq = blk_mq_alloc_request(q, nvme_req_op(req->cmd), 0);
if (IS_ERR(rq)) {
status = NVME_SC_INTERNAL;
goto out_put_ns;
}
nvme_init_request(rq, req->cmd);
if (timeout)
rq->timeout = timeout;
if (req->sg_cnt) {
ret = nvmet_passthru_map_sg(req, rq);
if (unlikely(ret)) {
status = NVME_SC_INTERNAL;
goto out_put_req;
}
}
/*
* If a command needs post-execution fixups, or there are any
* non-trivial effects, make sure to execute the command synchronously
* in a workqueue so that nvme_passthru_end gets called.
*/
effects = nvme_command_effects(ctrl, ns, req->cmd->common.opcode);
if (req->p.use_workqueue ||
(effects & ~(NVME_CMD_EFFECTS_CSUPP | NVME_CMD_EFFECTS_LBCC))) {
INIT_WORK(&req->p.work, nvmet_passthru_execute_cmd_work);
req->p.rq = rq;
queue_work(nvmet_wq, &req->p.work);
} else {
rq->end_io = nvmet_passthru_req_done;
rq->end_io_data = req;
blk_execute_rq_nowait(rq, false);
}
if (ns)
nvme_put_ns(ns);
return;
out_put_req:
blk_mq_free_request(rq);
out_put_ns:
if (ns)
nvme_put_ns(ns);
out:
nvmet_req_complete(req, status);
}
/*
* We need to emulate set host behaviour to ensure that any requested
* behaviour of the target's host matches the requested behaviour
* of the device's host and fail otherwise.
*/
static void nvmet_passthru_set_host_behaviour(struct nvmet_req *req)
{
struct nvme_ctrl *ctrl = nvmet_req_subsys(req)->passthru_ctrl;
struct nvme_feat_host_behavior *host;
u16 status = NVME_SC_INTERNAL;
int ret;
host = kzalloc(sizeof(*host) * 2, GFP_KERNEL);
if (!host)
goto out_complete_req;
ret = nvme_get_features(ctrl, NVME_FEAT_HOST_BEHAVIOR, 0,
host, sizeof(*host), NULL);
if (ret)
goto out_free_host;
status = nvmet_copy_from_sgl(req, 0, &host[1], sizeof(*host));
if (status)
goto out_free_host;
if (memcmp(&host[0], &host[1], sizeof(host[0]))) {
pr_warn("target host has requested different behaviour from the local host\n");
status = NVME_SC_INTERNAL;
}
out_free_host:
kfree(host);
out_complete_req:
nvmet_req_complete(req, status);
}
static u16 nvmet_setup_passthru_command(struct nvmet_req *req)
{
req->p.use_workqueue = false;
req->execute = nvmet_passthru_execute_cmd;
return NVME_SC_SUCCESS;
}
u16 nvmet_parse_passthru_io_cmd(struct nvmet_req *req)
{
/* Reject any commands with non-sgl flags set (ie. fused commands) */
if (req->cmd->common.flags & ~NVME_CMD_SGL_ALL)
return NVME_SC_INVALID_FIELD;
switch (req->cmd->common.opcode) {
case nvme_cmd_resv_register:
case nvme_cmd_resv_report:
case nvme_cmd_resv_acquire:
case nvme_cmd_resv_release:
/*
* Reservations cannot be supported properly because the
* underlying device has no way of differentiating different
* hosts that connect via fabrics. This could potentially be
* emulated in the future if regular targets grow support for
* this feature.
*/
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
return nvmet_setup_passthru_command(req);
}
/*
* Only features that are emulated or specifically allowed in the list are
* passed down to the controller. This function implements the allow list for
* both get and set features.
*/
static u16 nvmet_passthru_get_set_features(struct nvmet_req *req)
{
switch (le32_to_cpu(req->cmd->features.fid)) {
case NVME_FEAT_ARBITRATION:
case NVME_FEAT_POWER_MGMT:
case NVME_FEAT_LBA_RANGE:
case NVME_FEAT_TEMP_THRESH:
case NVME_FEAT_ERR_RECOVERY:
case NVME_FEAT_VOLATILE_WC:
case NVME_FEAT_WRITE_ATOMIC:
case NVME_FEAT_AUTO_PST:
case NVME_FEAT_TIMESTAMP:
case NVME_FEAT_HCTM:
case NVME_FEAT_NOPSC:
case NVME_FEAT_RRL:
case NVME_FEAT_PLM_CONFIG:
case NVME_FEAT_PLM_WINDOW:
case NVME_FEAT_HOST_BEHAVIOR:
case NVME_FEAT_SANITIZE:
case NVME_FEAT_VENDOR_START ... NVME_FEAT_VENDOR_END:
return nvmet_setup_passthru_command(req);
case NVME_FEAT_ASYNC_EVENT:
/* There is no support for forwarding ASYNC events */
case NVME_FEAT_IRQ_COALESCE:
case NVME_FEAT_IRQ_CONFIG:
/* The IRQ settings will not apply to the target controller */
case NVME_FEAT_HOST_MEM_BUF:
/*
* Any HMB that's set will not be passed through and will
* not work as expected
*/
case NVME_FEAT_SW_PROGRESS:
/*
* The Pre-Boot Software Load Count doesn't make much
* sense for a target to export
*/
case NVME_FEAT_RESV_MASK:
case NVME_FEAT_RESV_PERSIST:
/* No reservations, see nvmet_parse_passthru_io_cmd() */
default:
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
}
}
u16 nvmet_parse_passthru_admin_cmd(struct nvmet_req *req)
{
/* Reject any commands with non-sgl flags set (ie. fused commands) */
if (req->cmd->common.flags & ~NVME_CMD_SGL_ALL)
return NVME_SC_INVALID_FIELD;
/*
* Passthru all vendor specific commands
*/
if (req->cmd->common.opcode >= nvme_admin_vendor_start)
return nvmet_setup_passthru_command(req);
switch (req->cmd->common.opcode) {
case nvme_admin_async_event:
req->execute = nvmet_execute_async_event;
return NVME_SC_SUCCESS;
case nvme_admin_keep_alive:
/*
* Most PCIe ctrls don't support keep alive cmd, we route keep
* alive to the non-passthru mode. In future please change this
* code when PCIe ctrls with keep alive support available.
*/
req->execute = nvmet_execute_keep_alive;
return NVME_SC_SUCCESS;
case nvme_admin_set_features:
switch (le32_to_cpu(req->cmd->features.fid)) {
case NVME_FEAT_ASYNC_EVENT:
case NVME_FEAT_KATO:
case NVME_FEAT_NUM_QUEUES:
case NVME_FEAT_HOST_ID:
req->execute = nvmet_execute_set_features;
return NVME_SC_SUCCESS;
case NVME_FEAT_HOST_BEHAVIOR:
req->execute = nvmet_passthru_set_host_behaviour;
return NVME_SC_SUCCESS;
default:
return nvmet_passthru_get_set_features(req);
}
break;
case nvme_admin_get_features:
switch (le32_to_cpu(req->cmd->features.fid)) {
case NVME_FEAT_ASYNC_EVENT:
case NVME_FEAT_KATO:
case NVME_FEAT_NUM_QUEUES:
case NVME_FEAT_HOST_ID:
req->execute = nvmet_execute_get_features;
return NVME_SC_SUCCESS;
case NVME_FEAT_FDP:
return nvmet_setup_passthru_command(req);
default:
return nvmet_passthru_get_set_features(req);
}
break;
case nvme_admin_identify:
switch (req->cmd->identify.cns) {
case NVME_ID_CNS_CS_CTRL:
switch (req->cmd->identify.csi) {
case NVME_CSI_ZNS:
req->execute = nvmet_passthru_execute_cmd;
req->p.use_workqueue = true;
return NVME_SC_SUCCESS;
}
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
case NVME_ID_CNS_CTRL:
case NVME_ID_CNS_NS:
case NVME_ID_CNS_NS_DESC_LIST:
req->execute = nvmet_passthru_execute_cmd;
req->p.use_workqueue = true;
return NVME_SC_SUCCESS;
case NVME_ID_CNS_CS_NS:
switch (req->cmd->identify.csi) {
case NVME_CSI_ZNS:
req->execute = nvmet_passthru_execute_cmd;
req->p.use_workqueue = true;
return NVME_SC_SUCCESS;
}
return NVME_SC_INVALID_OPCODE | NVME_STATUS_DNR;
default:
return nvmet_setup_passthru_command(req);
}
case nvme_admin_get_log_page:
return nvmet_setup_passthru_command(req);
default:
/* Reject commands not in the allowlist above */
return nvmet_report_invalid_opcode(req);
}
}
int nvmet_passthru_ctrl_enable(struct nvmet_subsys *subsys)
{
struct nvme_ctrl *ctrl;
struct file *file;
int ret = -EINVAL;
void *old;
mutex_lock(&subsys->lock);
if (!subsys->passthru_ctrl_path)
goto out_unlock;
if (subsys->passthru_ctrl)
goto out_unlock;
if (subsys->nr_namespaces) {
pr_info("cannot enable both passthru and regular namespaces for a single subsystem");
goto out_unlock;
}
file = filp_open(subsys->passthru_ctrl_path, O_RDWR, 0);
if (IS_ERR(file)) {
ret = PTR_ERR(file);
goto out_unlock;
}
ctrl = nvme_ctrl_from_file(file);
if (!ctrl) {
pr_err("failed to open nvme controller %s\n",
subsys->passthru_ctrl_path);
goto out_put_file;
}
old = xa_cmpxchg(&passthru_subsystems, ctrl->instance, NULL,
subsys, GFP_KERNEL);
if (xa_is_err(old)) {
ret = xa_err(old);
goto out_put_file;
}
if (old)
goto out_put_file;
subsys->passthru_ctrl = ctrl;
subsys->ver = ctrl->vs;
if (subsys->ver < NVME_VS(1, 2, 1)) {
pr_warn("nvme controller version is too old: %llu.%llu.%llu, advertising 1.2.1\n",
NVME_MAJOR(subsys->ver), NVME_MINOR(subsys->ver),
NVME_TERTIARY(subsys->ver));
subsys->ver = NVME_VS(1, 2, 1);
}
nvme_get_ctrl(ctrl);
__module_get(subsys->passthru_ctrl->ops->module);
ret = 0;
out_put_file:
filp_close(file, NULL);
out_unlock:
mutex_unlock(&subsys->lock);
return ret;
}
static void __nvmet_passthru_ctrl_disable(struct nvmet_subsys *subsys)
{
if (subsys->passthru_ctrl) {
xa_erase(&passthru_subsystems, subsys->passthru_ctrl->instance);
module_put(subsys->passthru_ctrl->ops->module);
nvme_put_ctrl(subsys->passthru_ctrl);
}
subsys->passthru_ctrl = NULL;
subsys->ver = NVMET_DEFAULT_VS;
}
void nvmet_passthru_ctrl_disable(struct nvmet_subsys *subsys)
{
mutex_lock(&subsys->lock);
__nvmet_passthru_ctrl_disable(subsys);
mutex_unlock(&subsys->lock);
}
void nvmet_passthru_subsys_free(struct nvmet_subsys *subsys)
{
mutex_lock(&subsys->lock);
__nvmet_passthru_ctrl_disable(subsys);
mutex_unlock(&subsys->lock);
kfree(subsys->passthru_ctrl_path);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+441
View File
@@ -0,0 +1,441 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVM Express target device driver tracepoints
* Copyright (c) 2018 Johannes Thumshirn, SUSE Linux GmbH
*/
#include <linux/unaligned.h>
#include "trace.h"
static const char *nvmet_trace_admin_identify(struct trace_seq *p, u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 cns = cdw10[0];
u16 ctrlid = get_unaligned_le16(cdw10 + 2);
trace_seq_printf(p, "cns=%u, ctrlid=%u", cns, ctrlid);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_admin_get_features(struct trace_seq *p,
u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 fid = cdw10[0];
u8 sel = cdw10[1] & 0x7;
u32 cdw11 = get_unaligned_le32(cdw10 + 4);
trace_seq_printf(p, "fid=0x%x, sel=0x%x, cdw11=0x%x", fid, sel, cdw11);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_get_lba_status(struct trace_seq *p,
u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u64 slba = get_unaligned_le64(cdw10);
u32 mndw = get_unaligned_le32(cdw10 + 8);
u16 rl = get_unaligned_le16(cdw10 + 12);
u8 atype = cdw10[15];
trace_seq_printf(p, "slba=0x%llx, mndw=0x%x, rl=0x%x, atype=%u",
slba, mndw, rl, atype);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_admin_set_features(struct trace_seq *p,
u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 fid = cdw10[0];
u8 sv = cdw10[3] & 0x8;
u32 cdw11 = get_unaligned_le32(cdw10 + 4);
trace_seq_printf(p, "fid=0x%x, sv=0x%x, cdw11=0x%x", fid, sv, cdw11);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_read_write(struct trace_seq *p, u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u64 slba = get_unaligned_le64(cdw10);
u16 length = get_unaligned_le16(cdw10 + 8);
u16 control = get_unaligned_le16(cdw10 + 10);
u32 dsmgmt = get_unaligned_le32(cdw10 + 12);
u32 reftag = get_unaligned_le32(cdw10 + 16);
trace_seq_printf(p,
"slba=%llu, len=%u, ctrl=0x%x, dsmgmt=%u, reftag=%u",
slba, length, control, dsmgmt, reftag);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_dsm(struct trace_seq *p, u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "nr=%u, attributes=%u",
get_unaligned_le32(cdw10),
get_unaligned_le32(cdw10 + 4));
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_common(struct trace_seq *p, u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "cdw10=%*ph", 24, cdw10);
trace_seq_putc(p, 0);
return ret;
}
const char *nvmet_trace_parse_admin_cmd(struct trace_seq *p,
u8 opcode, u8 *cdw10)
{
switch (opcode) {
case nvme_admin_identify:
return nvmet_trace_admin_identify(p, cdw10);
case nvme_admin_set_features:
return nvmet_trace_admin_set_features(p, cdw10);
case nvme_admin_get_features:
return nvmet_trace_admin_get_features(p, cdw10);
case nvme_admin_get_lba_status:
return nvmet_trace_get_lba_status(p, cdw10);
default:
return nvmet_trace_common(p, cdw10);
}
}
static const char *nvmet_trace_zone_mgmt_send(struct trace_seq *p, u8 *cdw10)
{
static const char * const zsa_strs[] = {
[0x01] = "close zone",
[0x02] = "finish zone",
[0x03] = "open zone",
[0x04] = "reset zone",
[0x05] = "offline zone",
[0x10] = "set zone descriptor extension"
};
const char *ret = trace_seq_buffer_ptr(p);
u64 slba = get_unaligned_le64(cdw10);
const char *zsa_str;
u8 zsa = cdw10[12];
u8 all = cdw10[13];
if (zsa < ARRAY_SIZE(zsa_strs) && zsa_strs[zsa])
zsa_str = zsa_strs[zsa];
else
zsa_str = "reserved";
trace_seq_printf(p, "slba=%llu, zsa=%u:%s, all=%u",
slba, zsa, zsa_str, all);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_zone_mgmt_recv(struct trace_seq *p, u8 *cdw10)
{
static const char * const zrasf_strs[] = {
[0x00] = "list all zones",
[0x01] = "list the zones in the ZSE: Empty state",
[0x02] = "list the zones in the ZSIO: Implicitly Opened state",
[0x03] = "list the zones in the ZSEO: Explicitly Opened state",
[0x04] = "list the zones in the ZSC: Closed state",
[0x05] = "list the zones in the ZSF: Full state",
[0x06] = "list the zones in the ZSRO: Read Only state",
[0x07] = "list the zones in the ZSO: Offline state",
[0x09] = "list the zones that have the zone attribute"
};
const char *ret = trace_seq_buffer_ptr(p);
u64 slba = get_unaligned_le64(cdw10);
u32 numd = get_unaligned_le32(&cdw10[8]);
u8 zra = cdw10[12];
u8 zrasf = cdw10[13];
const char *zrasf_str;
u8 pr = cdw10[14];
if (zrasf < ARRAY_SIZE(zrasf_strs) && zrasf_strs[zrasf])
zrasf_str = zrasf_strs[zrasf];
else
zrasf_str = "reserved";
trace_seq_printf(p, "slba=%llu, numd=%u, zra=%u, zrasf=%u:%s, pr=%u",
slba, numd, zra, zrasf, zrasf_str, pr);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_resv_reg(struct trace_seq *p, u8 *cdw10)
{
static const char * const rrega_strs[] = {
[0x00] = "register",
[0x01] = "unregister",
[0x02] = "replace",
};
const char *ret = trace_seq_buffer_ptr(p);
u8 rrega = cdw10[0] & 0x7;
u8 iekey = (cdw10[0] >> 3) & 0x1;
u8 ptpl = (cdw10[3] >> 6) & 0x3;
const char *rrega_str;
if (rrega < ARRAY_SIZE(rrega_strs) && rrega_strs[rrega])
rrega_str = rrega_strs[rrega];
else
rrega_str = "reserved";
trace_seq_printf(p, "rrega=%u:%s, iekey=%u, ptpl=%u",
rrega, rrega_str, iekey, ptpl);
trace_seq_putc(p, 0);
return ret;
}
static const char * const rtype_strs[] = {
[0x00] = "reserved",
[0x01] = "write exclusive",
[0x02] = "exclusive access",
[0x03] = "write exclusive registrants only",
[0x04] = "exclusive access registrants only",
[0x05] = "write exclusive all registrants",
[0x06] = "exclusive access all registrants",
};
static const char *nvmet_trace_resv_acq(struct trace_seq *p, u8 *cdw10)
{
static const char * const racqa_strs[] = {
[0x00] = "acquire",
[0x01] = "preempt",
[0x02] = "preempt and abort",
};
const char *ret = trace_seq_buffer_ptr(p);
u8 racqa = cdw10[0] & 0x7;
u8 iekey = (cdw10[0] >> 3) & 0x1;
u8 rtype = cdw10[1];
const char *racqa_str = "reserved";
const char *rtype_str = "reserved";
if (racqa < ARRAY_SIZE(racqa_strs) && racqa_strs[racqa])
racqa_str = racqa_strs[racqa];
if (rtype < ARRAY_SIZE(rtype_strs) && rtype_strs[rtype])
rtype_str = rtype_strs[rtype];
trace_seq_printf(p, "racqa=%u:%s, iekey=%u, rtype=%u:%s",
racqa, racqa_str, iekey, rtype, rtype_str);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_resv_rel(struct trace_seq *p, u8 *cdw10)
{
static const char * const rrela_strs[] = {
[0x00] = "release",
[0x01] = "clear",
};
const char *ret = trace_seq_buffer_ptr(p);
u8 rrela = cdw10[0] & 0x7;
u8 iekey = (cdw10[0] >> 3) & 0x1;
u8 rtype = cdw10[1];
const char *rrela_str = "reserved";
const char *rtype_str = "reserved";
if (rrela < ARRAY_SIZE(rrela_strs) && rrela_strs[rrela])
rrela_str = rrela_strs[rrela];
if (rtype < ARRAY_SIZE(rtype_strs) && rtype_strs[rtype])
rtype_str = rtype_strs[rtype];
trace_seq_printf(p, "rrela=%u:%s, iekey=%u, rtype=%u:%s",
rrela, rrela_str, iekey, rtype, rtype_str);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_resv_report(struct trace_seq *p, u8 *cdw10)
{
const char *ret = trace_seq_buffer_ptr(p);
u32 numd = get_unaligned_le32(cdw10);
u8 eds = cdw10[4] & 0x1;
trace_seq_printf(p, "numd=%u, eds=%u", numd, eds);
trace_seq_putc(p, 0);
return ret;
}
const char *nvmet_trace_parse_nvm_cmd(struct trace_seq *p,
u8 opcode, u8 *cdw10)
{
switch (opcode) {
case nvme_cmd_read:
case nvme_cmd_write:
case nvme_cmd_write_zeroes:
case nvme_cmd_zone_append:
return nvmet_trace_read_write(p, cdw10);
case nvme_cmd_dsm:
return nvmet_trace_dsm(p, cdw10);
case nvme_cmd_zone_mgmt_send:
return nvmet_trace_zone_mgmt_send(p, cdw10);
case nvme_cmd_zone_mgmt_recv:
return nvmet_trace_zone_mgmt_recv(p, cdw10);
case nvme_cmd_resv_register:
return nvmet_trace_resv_reg(p, cdw10);
case nvme_cmd_resv_acquire:
return nvmet_trace_resv_acq(p, cdw10);
case nvme_cmd_resv_release:
return nvmet_trace_resv_rel(p, cdw10);
case nvme_cmd_resv_report:
return nvmet_trace_resv_report(p, cdw10);
default:
return nvmet_trace_common(p, cdw10);
}
}
static const char *nvmet_trace_fabrics_property_set(struct trace_seq *p,
u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 attrib = spc[0];
u32 ofst = get_unaligned_le32(spc + 4);
u64 value = get_unaligned_le64(spc + 8);
trace_seq_printf(p, "attrib=%u, ofst=0x%x, value=0x%llx",
attrib, ofst, value);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_fabrics_connect(struct trace_seq *p,
u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
u16 recfmt = get_unaligned_le16(spc);
u16 qid = get_unaligned_le16(spc + 2);
u16 sqsize = get_unaligned_le16(spc + 4);
u8 cattr = spc[6];
u32 kato = get_unaligned_le32(spc + 8);
trace_seq_printf(p, "recfmt=%u, qid=%u, sqsize=%u, cattr=%u, kato=%u",
recfmt, qid, sqsize, cattr, kato);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_fabrics_property_get(struct trace_seq *p,
u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 attrib = spc[0];
u32 ofst = get_unaligned_le32(spc + 4);
trace_seq_printf(p, "attrib=%u, ofst=0x%x", attrib, ofst);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_fabrics_auth_send(struct trace_seq *p, u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 spsp0 = spc[1];
u8 spsp1 = spc[2];
u8 secp = spc[3];
u32 tl = get_unaligned_le32(spc + 4);
trace_seq_printf(p, "spsp0=%02x, spsp1=%02x, secp=%02x, tl=%u",
spsp0, spsp1, secp, tl);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_fabrics_auth_receive(struct trace_seq *p, u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
u8 spsp0 = spc[1];
u8 spsp1 = spc[2];
u8 secp = spc[3];
u32 al = get_unaligned_le32(spc + 4);
trace_seq_printf(p, "spsp0=%02x, spsp1=%02x, secp=%02x, al=%u",
spsp0, spsp1, secp, al);
trace_seq_putc(p, 0);
return ret;
}
static const char *nvmet_trace_fabrics_common(struct trace_seq *p, u8 *spc)
{
const char *ret = trace_seq_buffer_ptr(p);
trace_seq_printf(p, "specific=%*ph", 24, spc);
trace_seq_putc(p, 0);
return ret;
}
const char *nvmet_trace_parse_fabrics_cmd(struct trace_seq *p,
u8 fctype, u8 *spc)
{
switch (fctype) {
case nvme_fabrics_type_property_set:
return nvmet_trace_fabrics_property_set(p, spc);
case nvme_fabrics_type_connect:
return nvmet_trace_fabrics_connect(p, spc);
case nvme_fabrics_type_property_get:
return nvmet_trace_fabrics_property_get(p, spc);
case nvme_fabrics_type_auth_send:
return nvmet_trace_fabrics_auth_send(p, spc);
case nvme_fabrics_type_auth_receive:
return nvmet_trace_fabrics_auth_receive(p, spc);
default:
return nvmet_trace_fabrics_common(p, spc);
}
}
const char *nvmet_trace_disk_name(struct trace_seq *p, char *name)
{
const char *ret = trace_seq_buffer_ptr(p);
if (*name)
trace_seq_printf(p, "disk=%s, ", name);
trace_seq_putc(p, 0);
return ret;
}
const char *nvmet_trace_ctrl_id(struct trace_seq *p, u16 ctrl_id)
{
const char *ret = trace_seq_buffer_ptr(p);
/*
* XXX: We don't know the controller instance before executing the
* connect command itself because the connect command for the admin
* queue will not provide the cntlid which will be allocated in this
* command. In case of io queues, the controller instance will be
* mapped by the extra data of the connect command.
* If we can know the extra data of the connect command in this stage,
* we can update this print statement later.
*/
if (ctrl_id)
trace_seq_printf(p, "%d", ctrl_id);
else
trace_seq_printf(p, "_");
trace_seq_putc(p, 0);
return ret;
}
+170
View File
@@ -0,0 +1,170 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* NVM Express target device driver tracepoints
* Copyright (c) 2018 Johannes Thumshirn, SUSE Linux GmbH
*
* This is entirely based on drivers/nvme/host/trace.h
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM nvmet
#if !defined(_TRACE_NVMET_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_NVMET_H
#include <linux/nvme.h>
#include <linux/tracepoint.h>
#include <linux/trace_seq.h>
#include "nvmet.h"
const char *nvmet_trace_parse_admin_cmd(struct trace_seq *p, u8 opcode,
u8 *cdw10);
const char *nvmet_trace_parse_nvm_cmd(struct trace_seq *p, u8 opcode,
u8 *cdw10);
const char *nvmet_trace_parse_fabrics_cmd(struct trace_seq *p, u8 fctype,
u8 *spc);
#define parse_nvme_cmd(qid, opcode, fctype, cdw10) \
((opcode) == nvme_fabrics_command ? \
nvmet_trace_parse_fabrics_cmd(p, fctype, cdw10) : \
(qid ? \
nvmet_trace_parse_nvm_cmd(p, opcode, cdw10) : \
nvmet_trace_parse_admin_cmd(p, opcode, cdw10)))
const char *nvmet_trace_ctrl_id(struct trace_seq *p, u16 ctrl_id);
#define __print_ctrl_id(ctrl_id) \
nvmet_trace_ctrl_id(p, ctrl_id)
const char *nvmet_trace_disk_name(struct trace_seq *p, char *name);
#define __print_disk_name(name) \
nvmet_trace_disk_name(p, name)
#ifndef TRACE_HEADER_MULTI_READ
static inline u16 nvmet_req_to_ctrl_id(struct nvmet_req *req)
{
/*
* The queue and controller pointers are not valid until an association
* has been established.
*/
if (!req->sq || !req->sq->ctrl)
return 0;
return req->sq->ctrl->cntlid;
}
static inline void __assign_req_name(char *name, struct nvmet_req *req)
{
if (!req->ns) {
memset(name, 0, DISK_NAME_LEN);
return;
}
strscpy_pad(name, req->ns->device_path, DISK_NAME_LEN);
}
#endif
TRACE_EVENT(nvmet_req_init,
TP_PROTO(struct nvmet_req *req, struct nvme_command *cmd),
TP_ARGS(req, cmd),
TP_STRUCT__entry(
__field(struct nvme_command *, cmd)
__field(u16, ctrl_id)
__array(char, disk, DISK_NAME_LEN)
__field(int, qid)
__field(u16, cid)
__field(u8, opcode)
__field(u8, fctype)
__field(u8, flags)
__field(u32, nsid)
__field(u64, metadata)
__array(u8, cdw10, 24)
),
TP_fast_assign(
__entry->cmd = cmd;
__entry->ctrl_id = nvmet_req_to_ctrl_id(req);
__assign_req_name(__entry->disk, req);
__entry->qid = req->sq->qid;
__entry->cid = cmd->common.command_id;
__entry->opcode = cmd->common.opcode;
__entry->fctype = cmd->fabrics.fctype;
__entry->flags = cmd->common.flags;
__entry->nsid = le32_to_cpu(cmd->common.nsid);
__entry->metadata = le64_to_cpu(cmd->common.metadata);
memcpy(__entry->cdw10, &cmd->common.cdws,
sizeof(__entry->cdw10));
),
TP_printk("nvmet%s: %sqid=%d, cmdid=%u, nsid=%u, flags=%#x, "
"meta=%#llx, cmd=(%s, %s)",
__print_ctrl_id(__entry->ctrl_id),
__print_disk_name(__entry->disk),
__entry->qid, __entry->cid, __entry->nsid,
__entry->flags, __entry->metadata,
show_opcode_name(__entry->qid, __entry->opcode,
__entry->fctype),
parse_nvme_cmd(__entry->qid, __entry->opcode,
__entry->fctype, __entry->cdw10))
);
TRACE_EVENT(nvmet_req_complete,
TP_PROTO(struct nvmet_req *req),
TP_ARGS(req),
TP_STRUCT__entry(
__field(u16, ctrl_id)
__array(char, disk, DISK_NAME_LEN)
__field(int, qid)
__field(int, cid)
__field(u64, result)
__field(u16, status)
),
TP_fast_assign(
__entry->ctrl_id = nvmet_req_to_ctrl_id(req);
__entry->qid = req->cq->qid;
__entry->cid = req->cqe->command_id;
__entry->result = le64_to_cpu(req->cqe->result.u64);
__entry->status = le16_to_cpu(req->cqe->status) >> 1;
__assign_req_name(__entry->disk, req);
),
TP_printk("nvmet%s: %sqid=%d, cmdid=%u, res=%#llx, status=%#x",
__print_ctrl_id(__entry->ctrl_id),
__print_disk_name(__entry->disk),
__entry->qid, __entry->cid, __entry->result, __entry->status)
);
#define aer_name(aer) { aer, #aer }
TRACE_EVENT(nvmet_async_event,
TP_PROTO(struct nvmet_ctrl *ctrl, __le32 result),
TP_ARGS(ctrl, result),
TP_STRUCT__entry(
__field(int, ctrl_id)
__field(u32, result)
),
TP_fast_assign(
__entry->ctrl_id = ctrl->cntlid;
__entry->result = (le32_to_cpu(result) & 0xff00) >> 8;
),
TP_printk("nvmet%d: NVME_AEN=%#08x [%s]",
__entry->ctrl_id, __entry->result,
__print_symbolic(__entry->result,
aer_name(NVME_AER_NOTICE_NS_CHANGED),
aer_name(NVME_AER_NOTICE_ANA),
aer_name(NVME_AER_NOTICE_FW_ACT_STARTING),
aer_name(NVME_AER_NOTICE_DISC_CHANGED),
aer_name(NVME_AER_ERROR),
aer_name(NVME_AER_SMART),
aer_name(NVME_AER_CSS),
aer_name(NVME_AER_VS))
)
);
#undef aer_name
#endif /* _TRACE_NVMET_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>
+623
View File
@@ -0,0 +1,623 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVMe ZNS-ZBD command implementation.
* Copyright (C) 2021 Western Digital Corporation or its affiliates.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/nvme.h>
#include <linux/blkdev.h>
#include "nvmet.h"
/*
* We set the Memory Page Size Minimum (MPSMIN) for target controller to 0
* which gets added by 12 in the nvme_enable_ctrl() which results in 2^12 = 4k
* as page_shift value. When calculating the ZASL use shift by 12.
*/
#define NVMET_MPSMIN_SHIFT 12
static inline u8 nvmet_zasl(unsigned int zone_append_sects)
{
/*
* Zone Append Size Limit (zasl) is expressed as a power of 2 value
* with the minimum memory page size (i.e. 12) as unit.
*/
return ilog2(zone_append_sects >> (NVMET_MPSMIN_SHIFT - 9));
}
static int validate_conv_zones_cb(struct blk_zone *z,
unsigned int i, void *data)
{
if (z->type == BLK_ZONE_TYPE_CONVENTIONAL)
return -EOPNOTSUPP;
return 0;
}
bool nvmet_bdev_zns_enable(struct nvmet_ns *ns)
{
u8 zasl = nvmet_zasl(bdev_max_zone_append_sectors(ns->bdev));
struct gendisk *bd_disk = ns->bdev->bd_disk;
int ret;
if (ns->subsys->zasl) {
if (ns->subsys->zasl > zasl)
return false;
}
ns->subsys->zasl = zasl;
/*
* Generic zoned block devices may have a smaller last zone which is
* not supported by ZNS. Exclude zoned drives that have such smaller
* last zone.
*/
if (get_capacity(bd_disk) & (bdev_zone_sectors(ns->bdev) - 1))
return false;
/*
* ZNS does not define a conventional zone type. Use report zones
* to detect if the device has conventional zones and reject it if
* it does.
*/
ret = blkdev_report_zones(ns->bdev, 0, bdev_nr_zones(ns->bdev),
validate_conv_zones_cb, NULL);
if (ret < 0)
return false;
ns->blksize_shift = blksize_bits(bdev_logical_block_size(ns->bdev));
return true;
}
void nvmet_execute_identify_ctrl_zns(struct nvmet_req *req)
{
u8 zasl = req->sq->ctrl->subsys->zasl;
struct nvme_id_ctrl_zns *id;
u16 status;
id = kzalloc_obj(*id);
if (!id) {
status = NVME_SC_INTERNAL;
goto out;
}
id->zasl = min_not_zero(nvmet_ctrl_mdts(req), zasl);
status = nvmet_copy_to_sgl(req, 0, id, sizeof(*id));
kfree(id);
out:
nvmet_req_complete(req, status);
}
void nvmet_execute_identify_ns_zns(struct nvmet_req *req)
{
struct nvme_id_ns_zns *id_zns = NULL;
u64 zsze;
u16 status;
u32 mar, mor;
if (le32_to_cpu(req->cmd->identify.nsid) == NVME_NSID_ALL) {
req->error_loc = offsetof(struct nvme_identify, nsid);
status = NVME_SC_INVALID_NS | NVME_STATUS_DNR;
goto out;
}
id_zns = kzalloc_obj(*id_zns);
if (!id_zns) {
status = NVME_SC_INTERNAL;
goto out;
}
status = nvmet_req_find_ns(req);
if (status)
goto done;
if (nvmet_ns_revalidate(req->ns)) {
mutex_lock(&req->ns->subsys->lock);
nvmet_ns_changed(req->ns->subsys, req->ns->nsid);
mutex_unlock(&req->ns->subsys->lock);
}
if (!bdev_is_zoned(req->ns->bdev)) {
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
req->error_loc = offsetof(struct nvme_identify, nsid);
goto out;
}
zsze = (bdev_zone_sectors(req->ns->bdev) << 9) >>
req->ns->blksize_shift;
id_zns->lbafe[0].zsze = cpu_to_le64(zsze);
mor = bdev_max_open_zones(req->ns->bdev);
if (!mor)
mor = U32_MAX;
else
mor--;
id_zns->mor = cpu_to_le32(mor);
mar = bdev_max_active_zones(req->ns->bdev);
if (!mar)
mar = U32_MAX;
else
mar--;
id_zns->mar = cpu_to_le32(mar);
done:
status = nvmet_copy_to_sgl(req, 0, id_zns, sizeof(*id_zns));
out:
kfree(id_zns);
nvmet_req_complete(req, status);
}
static u16 nvmet_bdev_validate_zone_mgmt_recv(struct nvmet_req *req)
{
sector_t sect = nvmet_lba_to_sect(req->ns, req->cmd->zmr.slba);
u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
if (sect >= get_capacity(req->ns->bdev->bd_disk)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, slba);
return NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
}
if (out_bufsize < sizeof(struct nvme_zone_report)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, numd);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
if (req->cmd->zmr.zra != NVME_ZRA_ZONE_REPORT) {
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, zra);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
switch (req->cmd->zmr.pr) {
case 0:
case 1:
break;
default:
req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, pr);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
switch (req->cmd->zmr.zrasf) {
case NVME_ZRASF_ZONE_REPORT_ALL:
case NVME_ZRASF_ZONE_STATE_EMPTY:
case NVME_ZRASF_ZONE_STATE_IMP_OPEN:
case NVME_ZRASF_ZONE_STATE_EXP_OPEN:
case NVME_ZRASF_ZONE_STATE_CLOSED:
case NVME_ZRASF_ZONE_STATE_FULL:
case NVME_ZRASF_ZONE_STATE_READONLY:
case NVME_ZRASF_ZONE_STATE_OFFLINE:
break;
default:
req->error_loc =
offsetof(struct nvme_zone_mgmt_recv_cmd, zrasf);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
return NVME_SC_SUCCESS;
}
struct nvmet_report_zone_data {
struct nvmet_req *req;
u64 out_buf_offset;
u64 out_nr_zones;
u64 nr_zones;
u8 zrasf;
};
static int nvmet_bdev_report_zone_cb(struct blk_zone *z, unsigned i, void *d)
{
static const unsigned int nvme_zrasf_to_blk_zcond[] = {
[NVME_ZRASF_ZONE_STATE_EMPTY] = BLK_ZONE_COND_EMPTY,
[NVME_ZRASF_ZONE_STATE_IMP_OPEN] = BLK_ZONE_COND_IMP_OPEN,
[NVME_ZRASF_ZONE_STATE_EXP_OPEN] = BLK_ZONE_COND_EXP_OPEN,
[NVME_ZRASF_ZONE_STATE_CLOSED] = BLK_ZONE_COND_CLOSED,
[NVME_ZRASF_ZONE_STATE_READONLY] = BLK_ZONE_COND_READONLY,
[NVME_ZRASF_ZONE_STATE_FULL] = BLK_ZONE_COND_FULL,
[NVME_ZRASF_ZONE_STATE_OFFLINE] = BLK_ZONE_COND_OFFLINE,
};
struct nvmet_report_zone_data *rz = d;
if (rz->zrasf != NVME_ZRASF_ZONE_REPORT_ALL &&
z->cond != nvme_zrasf_to_blk_zcond[rz->zrasf])
return 0;
if (rz->nr_zones < rz->out_nr_zones) {
struct nvme_zone_descriptor zdesc = { };
u16 status;
zdesc.zcap = nvmet_sect_to_lba(rz->req->ns, z->capacity);
zdesc.zslba = nvmet_sect_to_lba(rz->req->ns, z->start);
zdesc.wp = nvmet_sect_to_lba(rz->req->ns, z->wp);
zdesc.za = z->reset ? 1 << 2 : 0;
zdesc.zs = z->cond << 4;
zdesc.zt = z->type;
status = nvmet_copy_to_sgl(rz->req, rz->out_buf_offset, &zdesc,
sizeof(zdesc));
if (status)
return -EINVAL;
rz->out_buf_offset += sizeof(zdesc);
}
rz->nr_zones++;
return 0;
}
static unsigned long nvmet_req_nr_zones_from_slba(struct nvmet_req *req)
{
unsigned int sect = nvmet_lba_to_sect(req->ns, req->cmd->zmr.slba);
return bdev_nr_zones(req->ns->bdev) - bdev_zone_no(req->ns->bdev, sect);
}
static unsigned long get_nr_zones_from_buf(struct nvmet_req *req, u32 bufsize)
{
if (bufsize <= sizeof(struct nvme_zone_report))
return 0;
return (bufsize - sizeof(struct nvme_zone_report)) /
sizeof(struct nvme_zone_descriptor);
}
static void nvmet_bdev_zone_zmgmt_recv_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, z.zmgmt_work);
sector_t start_sect = nvmet_lba_to_sect(req->ns, req->cmd->zmr.slba);
unsigned long req_slba_nr_zones = nvmet_req_nr_zones_from_slba(req);
u32 out_bufsize = (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
__le64 nr_zones;
u16 status;
int ret;
struct nvmet_report_zone_data rz_data = {
.out_nr_zones = get_nr_zones_from_buf(req, out_bufsize),
/* leave the place for report zone header */
.out_buf_offset = sizeof(struct nvme_zone_report),
.zrasf = req->cmd->zmr.zrasf,
.nr_zones = 0,
.req = req,
};
status = nvmet_bdev_validate_zone_mgmt_recv(req);
if (status)
goto out;
if (!req_slba_nr_zones) {
status = NVME_SC_SUCCESS;
goto out;
}
ret = blkdev_report_zones(req->ns->bdev, start_sect, req_slba_nr_zones,
nvmet_bdev_report_zone_cb, &rz_data);
if (ret < 0) {
status = NVME_SC_INTERNAL;
goto out;
}
/*
* When partial bit is set nr_zones must indicate the number of zone
* descriptors actually transferred.
*/
if (req->cmd->zmr.pr)
rz_data.nr_zones = min(rz_data.nr_zones, rz_data.out_nr_zones);
nr_zones = cpu_to_le64(rz_data.nr_zones);
status = nvmet_copy_to_sgl(req, 0, &nr_zones, sizeof(nr_zones));
out:
nvmet_req_complete(req, status);
}
void nvmet_bdev_execute_zone_mgmt_recv(struct nvmet_req *req)
{
INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zone_zmgmt_recv_work);
queue_work(zbd_wq, &req->z.zmgmt_work);
}
static inline enum req_op zsa_req_op(u8 zsa)
{
switch (zsa) {
case NVME_ZONE_OPEN:
return REQ_OP_ZONE_OPEN;
case NVME_ZONE_CLOSE:
return REQ_OP_ZONE_CLOSE;
case NVME_ZONE_FINISH:
return REQ_OP_ZONE_FINISH;
case NVME_ZONE_RESET:
return REQ_OP_ZONE_RESET;
default:
return REQ_OP_LAST;
}
}
static u16 blkdev_zone_mgmt_errno_to_nvme_status(int ret)
{
switch (ret) {
case 0:
return NVME_SC_SUCCESS;
case -EINVAL:
case -EIO:
return NVME_SC_ZONE_INVALID_TRANSITION | NVME_STATUS_DNR;
default:
return NVME_SC_INTERNAL;
}
}
struct nvmet_zone_mgmt_send_all_data {
unsigned long *zbitmap;
struct nvmet_req *req;
};
static int zmgmt_send_scan_cb(struct blk_zone *z, unsigned i, void *d)
{
struct nvmet_zone_mgmt_send_all_data *data = d;
switch (zsa_req_op(data->req->cmd->zms.zsa)) {
case REQ_OP_ZONE_OPEN:
switch (z->cond) {
case BLK_ZONE_COND_CLOSED:
break;
default:
return 0;
}
break;
case REQ_OP_ZONE_CLOSE:
switch (z->cond) {
case BLK_ZONE_COND_IMP_OPEN:
case BLK_ZONE_COND_EXP_OPEN:
break;
default:
return 0;
}
break;
case REQ_OP_ZONE_FINISH:
switch (z->cond) {
case BLK_ZONE_COND_IMP_OPEN:
case BLK_ZONE_COND_EXP_OPEN:
case BLK_ZONE_COND_CLOSED:
break;
default:
return 0;
}
break;
default:
return -EINVAL;
}
set_bit(i, data->zbitmap);
return 0;
}
static u16 nvmet_bdev_zone_mgmt_emulate_all(struct nvmet_req *req)
{
struct block_device *bdev = req->ns->bdev;
unsigned int nr_zones = bdev_nr_zones(bdev);
struct bio *bio = NULL;
sector_t sector = 0;
int ret;
struct nvmet_zone_mgmt_send_all_data d = {
.req = req,
};
d.zbitmap = kcalloc_node(BITS_TO_LONGS(nr_zones), sizeof(*(d.zbitmap)),
GFP_NOIO, bdev->bd_disk->node_id);
if (!d.zbitmap) {
ret = -ENOMEM;
goto out;
}
/* Scan and build bitmap of the eligible zones */
ret = blkdev_report_zones(bdev, 0, nr_zones, zmgmt_send_scan_cb, &d);
if (ret != nr_zones) {
if (ret > 0)
ret = -EIO;
goto out;
} else {
/* We scanned all the zones */
ret = 0;
}
while (sector < bdev_nr_sectors(bdev)) {
if (test_bit(disk_zone_no(bdev->bd_disk, sector), d.zbitmap)) {
bio = blk_next_bio(bio, bdev, 0,
zsa_req_op(req->cmd->zms.zsa) | REQ_SYNC,
GFP_KERNEL);
bio->bi_iter.bi_sector = sector;
/* This may take a while, so be nice to others */
cond_resched();
}
sector += bdev_zone_sectors(bdev);
}
if (bio) {
ret = submit_bio_wait(bio);
bio_put(bio);
}
out:
kfree(d.zbitmap);
return blkdev_zone_mgmt_errno_to_nvme_status(ret);
}
static u16 nvmet_bdev_execute_zmgmt_send_all(struct nvmet_req *req)
{
int ret;
switch (zsa_req_op(req->cmd->zms.zsa)) {
case REQ_OP_ZONE_RESET:
ret = blkdev_zone_mgmt(req->ns->bdev, REQ_OP_ZONE_RESET, 0,
get_capacity(req->ns->bdev->bd_disk));
if (ret < 0)
return blkdev_zone_mgmt_errno_to_nvme_status(ret);
break;
case REQ_OP_ZONE_OPEN:
case REQ_OP_ZONE_CLOSE:
case REQ_OP_ZONE_FINISH:
return nvmet_bdev_zone_mgmt_emulate_all(req);
default:
/* this is needed to quiet compiler warning */
req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd, zsa);
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
return NVME_SC_SUCCESS;
}
static void nvmet_bdev_zmgmt_send_work(struct work_struct *w)
{
struct nvmet_req *req = container_of(w, struct nvmet_req, z.zmgmt_work);
sector_t sect = nvmet_lba_to_sect(req->ns, req->cmd->zms.slba);
enum req_op op = zsa_req_op(req->cmd->zms.zsa);
struct block_device *bdev = req->ns->bdev;
sector_t zone_sectors = bdev_zone_sectors(bdev);
u16 status = NVME_SC_SUCCESS;
int ret;
if (op == REQ_OP_LAST) {
req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd, zsa);
status = NVME_SC_ZONE_INVALID_TRANSITION | NVME_STATUS_DNR;
goto out;
}
/* when select all bit is set slba field is ignored */
if (req->cmd->zms.select_all) {
status = nvmet_bdev_execute_zmgmt_send_all(req);
goto out;
}
if (sect >= get_capacity(bdev->bd_disk)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd, slba);
status = NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
goto out;
}
if (sect & (zone_sectors - 1)) {
req->error_loc = offsetof(struct nvme_zone_mgmt_send_cmd, slba);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
ret = blkdev_zone_mgmt(bdev, op, sect, zone_sectors);
if (ret < 0)
status = blkdev_zone_mgmt_errno_to_nvme_status(ret);
out:
nvmet_req_complete(req, status);
}
void nvmet_bdev_execute_zone_mgmt_send(struct nvmet_req *req)
{
INIT_WORK(&req->z.zmgmt_work, nvmet_bdev_zmgmt_send_work);
queue_work(zbd_wq, &req->z.zmgmt_work);
}
static void nvmet_bdev_zone_append_bio_done(struct bio *bio)
{
struct nvmet_req *req = bio->bi_private;
if (bio->bi_status == BLK_STS_OK) {
req->cqe->result.u64 =
nvmet_sect_to_lba(req->ns, bio->bi_iter.bi_sector);
}
nvmet_req_complete(req, blk_to_nvme_status(req, bio->bi_status));
nvmet_req_bio_put(req, bio);
}
void nvmet_bdev_execute_zone_append(struct nvmet_req *req)
{
sector_t sect = nvmet_lba_to_sect(req->ns, req->cmd->rw.slba);
const blk_opf_t opf = REQ_OP_ZONE_APPEND | REQ_SYNC | REQ_IDLE;
u16 status = NVME_SC_SUCCESS;
unsigned int total_len = 0;
struct scatterlist *sg;
u32 data_len = nvmet_rw_data_len(req);
struct bio *bio;
int sg_cnt;
/* Request is completed on len mismatch in nvmet_check_transfer_len() */
if (!nvmet_check_transfer_len(req, nvmet_rw_data_len(req)))
return;
if (data_len >
bdev_max_zone_append_sectors(req->ns->bdev) << SECTOR_SHIFT) {
req->error_loc = offsetof(struct nvme_rw_command, length);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
if (!req->sg_cnt) {
nvmet_req_complete(req, 0);
return;
}
if (sect >= get_capacity(req->ns->bdev->bd_disk)) {
req->error_loc = offsetof(struct nvme_rw_command, slba);
status = NVME_SC_LBA_RANGE | NVME_STATUS_DNR;
goto out;
}
if (sect & (bdev_zone_sectors(req->ns->bdev) - 1)) {
req->error_loc = offsetof(struct nvme_rw_command, slba);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
goto out;
}
if (nvmet_use_inline_bvec(req)) {
bio = &req->z.inline_bio;
bio_init(bio, req->ns->bdev, req->inline_bvec,
ARRAY_SIZE(req->inline_bvec), opf);
} else {
bio = bio_alloc(req->ns->bdev, req->sg_cnt, opf, GFP_KERNEL);
}
bio->bi_end_io = nvmet_bdev_zone_append_bio_done;
bio->bi_iter.bi_sector = sect;
bio->bi_private = req;
if (req->cmd->rw.control & cpu_to_le16(NVME_RW_FUA))
bio->bi_opf |= REQ_FUA;
for_each_sg(req->sg, sg, req->sg_cnt, sg_cnt) {
unsigned int len = sg->length;
if (bio_add_page(bio, sg_page(sg), len, sg->offset) != len) {
status = NVME_SC_INTERNAL;
goto out_put_bio;
}
total_len += len;
}
if (total_len != data_len) {
status = NVME_SC_INTERNAL | NVME_STATUS_DNR;
goto out_put_bio;
}
submit_bio(bio);
return;
out_put_bio:
nvmet_req_bio_put(req, bio);
out:
nvmet_req_complete(req, status);
}
u16 nvmet_bdev_zns_parse_io_cmd(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
switch (cmd->common.opcode) {
case nvme_cmd_zone_append:
req->execute = nvmet_bdev_execute_zone_append;
return 0;
case nvme_cmd_zone_mgmt_recv:
req->execute = nvmet_bdev_execute_zone_mgmt_recv;
return 0;
case nvme_cmd_zone_mgmt_send:
req->execute = nvmet_bdev_execute_zone_mgmt_send;
return 0;
default:
return nvmet_bdev_parse_io_cmd(req);
}
}