[PATCH v3 10/12] nvmet: serve the FDP log pages

Guixin Liu kanie at linux.alibaba.com
Thu Oct 29 05:28:31 PDT 2026


The bridge advertises the FDP log pages 20h to 23h as supported;
serve them.

The FDP Configurations log page (LID 20h) is constructed from the
snapshot taken at namespace enable time: one valid configuration
with a single reclaim group, the backing number of reclaim unit
handles, the handle types and the reclaim unit nominal size
reported by the backing device.  The page is addressed by the Endurance
Group Identifier in the Log Specific Identifier field.  Hosts first
request the 16 byte header to learn the page size and then the full
page; both request sizes are served from the same construction path.
Short reads and nonzero Log Page Offsets are served as windows of
the complete page, and a zero length forward is rejected so that no
command can reach the backing controller without a staging buffer.

The remaining pages are owned by the backing controller and are
forwarded to it over its admin queue, addressed by the Endurance
Group Identifier of the backing endurance group, so hosts read the
real reclaim unit handle usage, counters and events of the backing
device.

Signed-off-by: Guixin Liu <kanie at linux.alibaba.com>
---
 drivers/nvme/target/admin-cmd.c |  16 ++++
 drivers/nvme/target/fdp.c       | 138 ++++++++++++++++++++++++++++++++
 drivers/nvme/target/nvmet.h     |  11 +++
 3 files changed, 165 insertions(+)

diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
index 523083eb0cee..e4efee44f5f4 100644
--- a/drivers/nvme/target/admin-cmd.c
+++ b/drivers/nvme/target/admin-cmd.c
@@ -220,6 +220,16 @@ static void nvmet_execute_get_supported_log_pages(struct nvmet_req *req)
 	logs->lids[NVME_LOG_FEATURES] = cpu_to_le32(NVME_LIDS_LSUPP);
 	logs->lids[NVME_LOG_RMI] = cpu_to_le32(NVME_LIDS_LSUPP);
 	logs->lids[NVME_LOG_RESERVATION] = cpu_to_le32(NVME_LIDS_LSUPP);
+	if (nvmet_req_subsys(req)->nr_ns_fdp_enabled) {
+		logs->lids[NVME_LOG_FDP_CONFIGS] =
+			cpu_to_le32(NVME_LIDS_LSUPP);
+		logs->lids[NVME_LOG_FDP_RUH_USAGE] =
+			cpu_to_le32(NVME_LIDS_LSUPP);
+		logs->lids[NVME_LOG_FDP_STATS] =
+			cpu_to_le32(NVME_LIDS_LSUPP);
+		logs->lids[NVME_LOG_FDP_EVENTS] =
+			cpu_to_le32(NVME_LIDS_LSUPP);
+	}
 
 	status = nvmet_copy_to_sgl(req, 0, logs, sizeof(*logs));
 	kfree(logs);
@@ -654,6 +664,12 @@ static void nvmet_execute_get_log_page(struct nvmet_req *req)
 		return nvmet_execute_get_log_page_rmi(req);
 	case NVME_LOG_RESERVATION:
 		return nvmet_execute_get_log_page_resv(req);
+	case NVME_LOG_FDP_CONFIGS:
+		return nvmet_execute_get_log_page_fdp_configs(req);
+	case NVME_LOG_FDP_RUH_USAGE:
+	case NVME_LOG_FDP_STATS:
+	case NVME_LOG_FDP_EVENTS:
+		return nvmet_fdp_log_forward(req);
 	}
 	pr_debug("unhandled lid %d on qid %d\n",
 	       req->cmd->get_log_page.lid, req->sq->qid);
diff --git a/drivers/nvme/target/fdp.c b/drivers/nvme/target/fdp.c
index f615b2bdc3d6..dbd0250a1ee6 100644
--- a/drivers/nvme/target/fdp.c
+++ b/drivers/nvme/target/fdp.c
@@ -542,3 +542,141 @@ static int nvmet_fdp_init_backend(struct nvmet_ns *ns)
 	kfree(id);
 	return ret;
 }
+
+static void nvmet_fdp_configs_work(struct nvmet_req *req)
+{
+	u32 lsi = le16_to_cpu(req->cmd->get_log_page.lsi);
+	u64 lpo = nvmet_get_log_page_offset(req->cmd);
+	struct nvme_fdp_config_desc *desc;
+	struct nvme_fdp_config_log *log;
+	size_t dsze, total, avail, len;
+	struct nvmet_ns *ns;
+	u16 status = 0;
+	int i;
+
+	if (req->transfer_len > NVMET_FDP_MAX_TRANSFER_SIZE) {
+		status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+		goto out;
+	}
+
+	ns = nvmet_fdp_lookup_ns(req, lsi, true);
+	if (!ns) {
+		status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+		goto out;
+	}
+
+	dsze = ALIGN(struct_size(desc, ruhs, ns->fdp.nruh), 8);
+	total = sizeof(*log) + dsze;
+	len = max(total, req->transfer_len);
+
+	/* single configuration, 0's based */
+	log = kzalloc(len, GFP_KERNEL);
+	if (!log) {
+		status = NVME_SC_INTERNAL;
+		goto out_put;
+	}
+
+	log->numfdpc = 0;
+	log->ver = 0;
+	log->sze = cpu_to_le32(total);
+	desc = (void *)(log + 1);
+	desc->dsze = cpu_to_le16(dsze);
+	/* FDPCV set, FDPVWC clear, RGIF 0 for the single RG */
+	desc->fdpa = 1 << 7;
+	desc->nrg = cpu_to_le32(1);
+	desc->nruh = cpu_to_le16(ns->fdp.nruh);
+	desc->maxpids = cpu_to_le16(ns->fdp.nruh - 1);
+	desc->nns = cpu_to_le32(1);
+	desc->runs = cpu_to_le64(ns->fdp.runs);
+	for (i = 0; i < ns->fdp.nruh; i++)
+		desc->ruhs[i].ruht = ns->fdp.ruht[i];
+
+	if (lpo >= total) {
+		status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+		goto free_log;
+	}
+
+	/*
+	 * Serve the requested window of the complete log: a short read
+	 * gets the true prefix, and a nonzero Log Page Offset gets the
+	 * bytes at that offset, with zeroes only past the end of the log.
+	 */
+	avail = total - lpo;
+	memmove(log, (void *)log + lpo, avail);
+	memset((void *)log + avail, 0, len - avail);
+	status = nvmet_copy_to_sgl(req, 0, log, req->transfer_len);
+free_log:
+	kfree(log);
+out_put:
+	percpu_ref_put(&ns->ref);
+out:
+	nvmet_req_complete(req, status);
+}
+
+void nvmet_execute_get_log_page_fdp_configs(struct nvmet_req *req)
+{
+	nvmet_fdp_defer(req, nvmet_fdp_configs_work);
+}
+
+/*
+ * Forward an FDP log page that the backing controller owns, addressed
+ * by the Endurance Group Identifier of the backing endurance group.
+ */
+static void nvmet_fdp_log_forward_work(struct nvmet_req *req)
+{
+	struct nvme_command c = {};
+	struct nvmet_ns *ns;
+	u16 status = 0;
+	void *buf;
+	u32 numd;
+	int ret;
+
+	if (!req->transfer_len ||
+	    req->transfer_len > NVMET_FDP_MAX_TRANSFER_SIZE) {
+		status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+		goto out;
+	}
+
+	ns = nvmet_fdp_lookup_ns(req,
+				 le16_to_cpu(req->cmd->get_log_page.lsi),
+				 true);
+	if (!ns) {
+		status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+		goto out;
+	}
+
+	buf = kzalloc(req->transfer_len, GFP_KERNEL);
+	if (!buf) {
+		status = NVME_SC_INTERNAL;
+		goto out_put;
+	}
+
+	c = *req->cmd;
+	c.get_log_page.nsid = cpu_to_le32(0);
+	c.get_log_page.lsi = cpu_to_le16(ns->fdp.backend_endgid);
+	c.common.metadata = 0;
+	c.common.dptr = (union nvme_data_ptr){};
+	/* the backend must not transfer more than the staging buffer holds */
+	numd = nvme_bytes_to_numd(req->transfer_len);
+	c.get_log_page.numdl = cpu_to_le16(numd);
+	c.get_log_page.numdu = cpu_to_le16(numd >> 16);
+	ret = nvme_submit_sync_cmd(ns->fdp.backend_ctrl->admin_q, &c, buf,
+				   req->transfer_len);
+	if (ret) {
+		status = ret > 0 ? ret : NVME_SC_INTERNAL;
+		goto free;
+	}
+
+	status = nvmet_copy_to_sgl(req, 0, buf, req->transfer_len);
+free:
+	kfree(buf);
+out_put:
+	percpu_ref_put(&ns->ref);
+out:
+	nvmet_req_complete(req, status);
+}
+
+void nvmet_fdp_log_forward(struct nvmet_req *req)
+{
+	nvmet_fdp_defer(req, nvmet_fdp_log_forward_work);
+}
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 0a20c22b0b23..c61cfe66f336 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -1008,6 +1008,8 @@ u16 nvmet_set_feat_fdp(struct nvmet_req *req);
 u16 nvmet_get_feat_fdp(struct nvmet_req *req);
 void nvmet_execute_io_mgmt_recv(struct nvmet_req *req);
 void nvmet_execute_io_mgmt_send(struct nvmet_req *req);
+void nvmet_execute_get_log_page_fdp_configs(struct nvmet_req *req);
+void nvmet_fdp_log_forward(struct nvmet_req *req);
 #else
 static inline bool nvmet_fdp_enabled(struct nvmet_ns *ns)
 {
@@ -1036,6 +1038,15 @@ static inline void nvmet_execute_io_mgmt_send(struct nvmet_req *req)
 {
 	nvmet_req_complete(req, NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
 }
+static inline void nvmet_execute_get_log_page_fdp_configs(
+		struct nvmet_req *req)
+{
+	nvmet_req_complete(req, NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
+}
+static inline void nvmet_fdp_log_forward(struct nvmet_req *req)
+{
+	nvmet_req_complete(req, NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
+}
 #endif
 
 /*
-- 
2.43.7




More information about the Linux-nvme mailing list