[PATCH v3 08/12] nvmet: implement the FDP feature
Guixin Liu
kanie at linux.alibaba.com
Thu Oct 29 05:28:29 PDT 2026
Implement Set Features and Get Features for FID 1Dh. The Endurance
Group Identifier in Command Dword 11 names the namespace directly,
since nvmet identifies each namespace as its own endurance group
(endgid = nsid). Get Features reports FDPE set with FDPCIDX 0 for bridged
namespaces, and FDPE clear for the others, so that a subsystem mixing
both kinds does not fail the per-namespace discovery the host runs
on every namespace.
Set Features is served locally: the bridge runs the one configuration
that the backing device is already in, so setting the feature to that
same value is a no-op that succeeds, and any other value is a change
of a feature whose endurance group has namespaces, which the spec
aborts with Command Sequence Error. That also covers disabling,
which would strand the write streams already handed out to hosts.
The namespace lookup runs under RCU and takes a namespace reference,
so that a namespace being disabled concurrently with the command
cannot go away under it. Report the feature in the Features log
page under the same per-subsystem condition as the FDPS bit.
Signed-off-by: Guixin Liu <kanie at linux.alibaba.com>
---
drivers/nvme/target/admin-cmd.c | 9 +++
drivers/nvme/target/fdp.c | 111 ++++++++++++++++++++++++++++++++
drivers/nvme/target/nvmet.h | 10 +++
3 files changed, 130 insertions(+)
diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
index 8a8ab2af3882..6d0b1df3ae96 100644
--- a/drivers/nvme/target/admin-cmd.c
+++ b/drivers/nvme/target/admin-cmd.c
@@ -600,6 +600,9 @@ static void nvmet_execute_get_log_page_features(struct nvmet_req *req)
cpu_to_le32(NVME_FIS_FSUPP | NVME_FIS_NSCPE);
features->fis[NVME_FEAT_RESV_MASK] =
cpu_to_le32(NVME_FIS_FSUPP | NVME_FIS_NSCPE);
+ if (nvmet_req_subsys(req)->nr_ns_fdp_enabled)
+ features->fis[NVME_FEAT_FDP] =
+ cpu_to_le32(NVME_FIS_FSUPP | NVME_FIS_EGSCPE);
status = nvmet_copy_to_sgl(req, 0, features, sizeof(*features));
kfree(features);
@@ -1385,6 +1388,9 @@ void nvmet_execute_set_features(struct nvmet_req *req)
case NVME_FEAT_RESV_MASK:
status = nvmet_set_feat_resv_notif_mask(req, cdw11);
break;
+ case NVME_FEAT_FDP:
+ status = nvmet_set_feat_fdp(req);
+ break;
default:
req->error_loc = offsetof(struct nvme_common_command, cdw10);
status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
@@ -1562,6 +1568,9 @@ void nvmet_execute_get_features(struct nvmet_req *req)
case NVME_FEAT_RESV_MASK:
status = nvmet_get_feat_resv_notif_mask(req);
break;
+ case NVME_FEAT_FDP:
+ status = nvmet_get_feat_fdp(req);
+ break;
default:
req->error_loc =
offsetof(struct nvme_common_command, cdw10);
diff --git a/drivers/nvme/target/fdp.c b/drivers/nvme/target/fdp.c
index 761b9029ce25..ae23a6078745 100644
--- a/drivers/nvme/target/fdp.c
+++ b/drivers/nvme/target/fdp.c
@@ -12,6 +12,10 @@
#include "../host/nvme.h"
#include "nvmet.h"
+/* Flexible Data Placement feature (FID 1Dh), Command Dword 12 */
+#define NVME_FDP_CDW12_FDPE (1U << 0)
+#define NVME_FDP_CDW12_FDPCIDX_MASK (0xffU << 8)
+
int nvmet_fdp_init_ns(struct nvmet_ns *ns)
{
struct block_device *bdev = ns->bdev;
@@ -65,3 +69,110 @@ void nvmet_fdp_exit_ns(struct nvmet_ns *ns)
ns->fdp.backend_q = NULL;
ns->fdp.backend_ctrl = NULL;
}
+
+
+/*
+ * Each namespace is its own endurance group, so the Endurance Group
+ * Identifier in Command Dword 11, and in the Log Specific Identifier
+ * of the FDP log pages, directly names the namespace. Returns the
+ * namespace with a reference held.
+ */
+static struct nvmet_ns *nvmet_fdp_lookup_ns(struct nvmet_req *req,
+ u32 endgid, bool check_fdp)
+{
+ struct nvmet_subsys *subsys = nvmet_req_subsys(req);
+ struct nvmet_ns *ns;
+
+ rcu_read_lock();
+ ns = xa_load(&subsys->namespaces, endgid);
+ if (!ns || !test_bit(NVMET_NS_IO_LIVE, &ns->flags) ||
+ (check_fdp && !nvmet_fdp_enabled(ns)))
+ goto out_unlock;
+
+ if (!percpu_ref_tryget_live_rcu(&ns->ref))
+ goto out_unlock;
+
+ rcu_read_unlock();
+ return ns;
+
+out_unlock:
+ rcu_read_unlock();
+ return NULL;
+}
+
+/*
+ * The bridge runs the one configuration that the backing device is
+ * already in, so setting the feature to that same value is the only
+ * transition that can be served. The spec aborts any other value
+ * change of a feature whose endurance group has namespaces with
+ * Command Sequence Error, which also covers disabling: pulling FDP
+ * from under a live namespace would strand the write streams already
+ * handed out to hosts.
+ */
+u16 nvmet_set_feat_fdp(struct nvmet_req *req)
+{
+ u32 cdw12 = le32_to_cpu(req->cmd->features.dword12);
+ struct nvmet_ns *ns;
+ u16 status;
+
+ ns = nvmet_fdp_lookup_ns(req,
+ le32_to_cpu(req->cmd->features.dword11),
+ false);
+ if (!ns) {
+ req->error_loc = offsetof(struct nvme_features, dword11);
+ return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ }
+
+ if (cdw12 & ~(NVME_FDP_CDW12_FDPCIDX_MASK | NVME_FDP_CDW12_FDPE)) {
+ req->error_loc = offsetof(struct nvme_features, dword12);
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ } else if (!nvmet_fdp_enabled(ns)) {
+ /*
+ * A namespace without FDP reports FDPE clear, so setting
+ * that same disabled configuration is a no-op; enabling
+ * it is a configuration change the bridge cannot serve.
+ */
+ if (cdw12) {
+ status = NVME_SC_CMD_SEQ_ERROR | NVME_STATUS_DNR;
+ } else {
+ nvmet_set_result(req, 0);
+ status = 0;
+ }
+ } else if ((cdw12 & NVME_FDP_CDW12_FDPE) &&
+ !(cdw12 & NVME_FDP_CDW12_FDPCIDX_MASK)) {
+ nvmet_set_result(req, FDPCFG_FDPE);
+ status = 0;
+ } else {
+ status = NVME_SC_CMD_SEQ_ERROR | NVME_STATUS_DNR;
+ }
+
+ percpu_ref_put(&ns->ref);
+ return status;
+}
+
+u16 nvmet_get_feat_fdp(struct nvmet_req *req)
+{
+ struct nvmet_ns *ns;
+
+ ns = nvmet_fdp_lookup_ns(req,
+ le32_to_cpu(req->cmd->features.dword11),
+ false);
+ if (!ns) {
+ req->error_loc = offsetof(struct nvme_features, dword11);
+ return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ }
+
+ /*
+ * The controller may serve namespaces without FDP next to bridged
+ * ones; report FDP as disabled for those instead of failing the
+ * Get Features command the host issues for every namespace.
+ */
+ if (nvmet_fdp_enabled(ns))
+ /* the namespace is up, so FDP is enabled on the backing device */
+ nvmet_set_result(req, FDPCFG_FDPE);
+ else
+ nvmet_set_result(req, 0);
+
+ percpu_ref_put(&ns->ref);
+ return 0;
+}
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 81defb4abfdd..5620492dd431 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -1004,6 +1004,8 @@ static inline bool nvmet_fdp_enabled(struct nvmet_ns *ns)
}
int nvmet_fdp_init_ns(struct nvmet_ns *ns);
void nvmet_fdp_exit_ns(struct nvmet_ns *ns);
+u16 nvmet_set_feat_fdp(struct nvmet_req *req);
+u16 nvmet_get_feat_fdp(struct nvmet_req *req);
#else
static inline bool nvmet_fdp_enabled(struct nvmet_ns *ns)
{
@@ -1016,6 +1018,14 @@ static inline int nvmet_fdp_init_ns(struct nvmet_ns *ns)
static inline void nvmet_fdp_exit_ns(struct nvmet_ns *ns)
{
}
+static inline u16 nvmet_set_feat_fdp(struct nvmet_req *req)
+{
+ return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+}
+static inline u16 nvmet_get_feat_fdp(struct nvmet_req *req)
+{
+ return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+}
#endif
/*
--
2.43.7
More information about the Linux-nvme
mailing list