From: Guixin Liu <kanie@linux.alibaba.com>
To: Keith Busch <kbusch@kernel.org>, Jens Axboe <axboe@kernel.dk>,
Christoph Hellwig <hch@lst.de>, Sagi Grimberg <sagi@grimberg.me>,
Nilay Shroff <nilay@linux.ibm.com>,
Daniel Wagner <dwagner@suse.de>, Hannes Reinecke <hare@suse.de>
Cc: linux-nvme@lists.infradead.org
Subject: [PATCH 08/11] nvmet: forward the FDP I/O management commands to the backing device
Date: Mon, 19 Oct 2026 11:36:21 +0800 [thread overview]
Message-ID: <20261019033624.402337-9-kanie@linux.alibaba.com> (raw)
In-Reply-To: <20261019033624.402337-1-kanie@linux.alibaba.com>
Dispatch the I/O Management Receive and Send commands from the I/O
command parser, with the transfer length derived from NUMD and NPID,
respectively, and report both commands as supported in the Commands
Supported and Effects log page.
Both are forwarded to the backing namespace: the data is staged
through a linear kernel buffer, which is what the backend submission
path takes, so the reclaim unit handle status hosts read reflects
the real reclaim units of the backing device, and a Reclaim Unit
Handle Update is carried out by the controller that owns that state.
When the namespace is enabled, learn the backing FDP geometry that
the forwarding needs: the Endurance Group Identifier the backing FDP
state is addressed by, and the placement identifier list of the
Reclaim Unit Handle Status. The bridge translates placement
identifiers to write streams by their index, so the enable fails
when the backing placement identifiers are not identity mapped.
Assisted-by: LLM
Signed-off-by: Guixin Liu <kanie@linux.alibaba.com>
---
drivers/nvme/target/admin-cmd.c | 5 +
drivers/nvme/target/core.c | 26 ++++-
drivers/nvme/target/fdp.c | 177 +++++++++++++++++++++++++++++++-
drivers/nvme/target/nvmet.h | 12 +++
4 files changed, 217 insertions(+), 3 deletions(-)
diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
index 93fb9c3d500a3..34492cf02875a 100644
--- a/drivers/nvme/target/admin-cmd.c
+++ b/drivers/nvme/target/admin-cmd.c
@@ -394,6 +394,11 @@ static void nvmet_get_cmd_effects_nvm(struct nvme_effects_log *log)
log->iocs[nvme_cmd_write] =
log->iocs[nvme_cmd_write_zeroes] =
cpu_to_le32(NVME_CMD_EFFECTS_CSUPP | NVME_CMD_EFFECTS_LBCC);
+ if (IS_ENABLED(CONFIG_NVME_TARGET_FDP)) {
+ log->iocs[nvme_cmd_io_mgmt_recv] =
+ log->iocs[nvme_cmd_io_mgmt_send] =
+ cpu_to_le32(NVME_CMD_EFFECTS_CSUPP);
+ }
}
static void nvmet_get_cmd_effects_zns(struct nvme_effects_log *log)
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 4a6444fdfb2f8..70ae2581812bd 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -1097,9 +1097,22 @@ static u32 nvmet_io_cmd_transfer_len(struct nvmet_req *req)
return nvmet_dsm_len(req);
case nvme_cmd_zone_mgmt_recv:
return (le32_to_cpu(req->cmd->zmr.numd) + 1) << 2;
+ case nvme_cmd_io_mgmt_recv:
+ if (IS_ENABLED(CONFIG_NVME_TARGET_FDP))
+ return (le32_to_cpu(req->cmd->imr.numd) + 1) << 2;
+ break;
+ case nvme_cmd_io_mgmt_send:
+ if (IS_ENABLED(CONFIG_NVME_TARGET_FDP)) {
+ u32 npid = le16_to_cpu(req->cmd->ims.mos);
+
+ return (npid + 1) * sizeof(__le16);
+ }
+ break;
default:
- return 0;
+ break;
}
+
+ return 0;
}
static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
@@ -1141,6 +1154,17 @@ static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
return ret;
}
+ if (IS_ENABLED(CONFIG_NVME_TARGET_FDP)) {
+ switch (req->cmd->common.opcode) {
+ case nvme_cmd_io_mgmt_recv:
+ req->execute = nvmet_execute_io_mgmt_recv;
+ return 0;
+ case nvme_cmd_io_mgmt_send:
+ req->execute = nvmet_execute_io_mgmt_send;
+ return 0;
+ }
+ }
+
switch (req->ns->csi) {
case NVME_CSI_NVM:
if (req->ns->file)
diff --git a/drivers/nvme/target/fdp.c b/drivers/nvme/target/fdp.c
index 0327970a9cb9d..6458efadbcb8e 100644
--- a/drivers/nvme/target/fdp.c
+++ b/drivers/nvme/target/fdp.c
@@ -34,6 +34,8 @@ bool nvmet_fdp_subsys_configured(struct nvmet_subsys *subsys)
return ret;
}
+static int nvmet_fdp_init_backend(struct nvmet_ns *ns);
+
int nvmet_fdp_init_ns(struct nvmet_ns *ns)
{
struct block_device *bdev = ns->bdev;
@@ -78,8 +80,9 @@ int nvmet_fdp_init_ns(struct nvmet_ns *ns)
backend_ns = bdev->bd_disk->private_data;
ns->fdp.backend_q = backend_ns->queue;
ns->fdp.backend_ctrl = backend_ns->ctrl;
+ ns->fdp.backend_nsid = backend_ns->head->ns_id;
- return 0;
+ return nvmet_fdp_init_backend(ns);
}
void nvmet_fdp_exit_ns(struct nvmet_ns *ns)
@@ -88,7 +91,6 @@ void nvmet_fdp_exit_ns(struct nvmet_ns *ns)
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
@@ -160,3 +162,174 @@ u16 nvmet_get_feat_fdp(struct nvmet_req *req)
return 0;
}
+
+static u16 nvmet_fdp_io_mgmt_check(struct nvmet_req *req)
+{
+ if (!nvmet_fdp_enabled(req->ns))
+ return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+
+ return 0;
+}
+
+/*
+ * The backend submission path takes a linear kernel buffer, so the
+ * request data is staged through one.
+ */
+static u16 nvmet_fdp_io_mgmt_forward(struct nvmet_req *req, bool recv)
+{
+ struct nvme_command c = {};
+ u16 status = 0;
+ void *buf;
+ int ret;
+
+ buf = kzalloc(req->transfer_len, GFP_KERNEL);
+ if (!buf)
+ return NVME_SC_INTERNAL;
+
+ if (!recv) {
+ status = nvmet_copy_from_sgl(req, 0, buf, req->transfer_len);
+ if (status)
+ goto free;
+ }
+
+ c.common = req->cmd->common;
+ c.common.nsid = cpu_to_le32(req->ns->fdp.backend_nsid);
+ ret = nvme_submit_sync_cmd(req->ns->fdp.backend_q, &c, buf,
+ req->transfer_len);
+ if (ret) {
+ status = ret > 0 ? ret : NVME_SC_INTERNAL;
+ goto free;
+ }
+
+ if (recv)
+ status = nvmet_copy_to_sgl(req, 0, buf, req->transfer_len);
+free:
+ kfree(buf);
+ return status;
+}
+
+void nvmet_execute_io_mgmt_recv(struct nvmet_req *req)
+{
+ u16 status;
+
+ status = nvmet_fdp_io_mgmt_check(req);
+ if (status)
+ goto out;
+
+ if (req->cmd->imr.mo != NVME_IO_MGMT_RECV_MO_RUHS) {
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ goto out;
+ }
+
+ status = nvmet_fdp_io_mgmt_forward(req, true);
+out:
+ nvmet_req_complete(req, status);
+}
+
+void nvmet_execute_io_mgmt_send(struct nvmet_req *req)
+{
+ u16 status;
+
+ status = nvmet_fdp_io_mgmt_check(req);
+ if (status)
+ goto out;
+
+ if (req->cmd->ims.mo != NVME_IO_MGMT_SEND_MO_RUH_UPDATE) {
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ goto out;
+ }
+
+ status = nvmet_fdp_io_mgmt_forward(req, false);
+out:
+ nvmet_req_complete(req, status);
+}
+
+static int nvmet_fdp_backend_identify(struct nvmet_ns *ns,
+ struct nvme_id_ns *id)
+{
+ struct nvme_command c = {};
+
+ c.identify.opcode = nvme_admin_identify;
+ c.identify.nsid = cpu_to_le32(ns->fdp.backend_nsid);
+ c.identify.cns = NVME_ID_CNS_NS;
+
+ return nvme_submit_sync_cmd(ns->fdp.backend_ctrl->admin_q, &c, id,
+ sizeof(*id));
+}
+
+static int nvmet_fdp_backend_ruhs(struct nvmet_ns *ns,
+ struct nvme_fdp_ruh_status *st)
+{
+ size_t len = struct_size(st, ruhsd, ns->fdp.nruh);
+ struct nvme_command c = {};
+
+ c.imr.opcode = nvme_cmd_io_mgmt_recv;
+ c.imr.nsid = cpu_to_le32(ns->fdp.backend_nsid);
+ c.imr.mo = NVME_IO_MGMT_RECV_MO_RUHS;
+ c.imr.numd = cpu_to_le32(nvme_bytes_to_numd(len));
+
+ return nvme_submit_sync_cmd(ns->fdp.backend_q, &c, st, len);
+}
+
+/*
+ * Learn the FDP geometry of the backing NVMe namespace: the
+ * Endurance Group Identifier its FDP state is addressed by, and the
+ * placement identifier list of the Reclaim Unit Handle Status. The
+ * placement translation relies on the identity mapping, so the
+ * enable fails when the backing geometry deviates from it.
+ */
+static int nvmet_fdp_init_backend(struct nvmet_ns *ns)
+{
+ struct nvme_fdp_ruh_status *st;
+ struct nvme_id_ns *id;
+ int ret, i;
+
+ id = kzalloc_obj(*id, GFP_KERNEL);
+ if (!id)
+ return -ENOMEM;
+
+ ret = nvmet_fdp_backend_identify(ns, id);
+ if (ret) {
+ pr_err("the ns:%u backing identify failed: %d\n",
+ ns->nsid, ret);
+ goto free_id;
+ }
+
+ ns->fdp.backend_endgid = le16_to_cpu(id->endgid);
+
+ st = kzalloc(struct_size(st, ruhsd, ns->fdp.nruh), GFP_KERNEL);
+ if (!st) {
+ ret = -ENOMEM;
+ goto free_id;
+ }
+
+ ret = nvmet_fdp_backend_ruhs(ns, st);
+ if (ret) {
+ pr_err("the ns:%u backing reclaim unit handle status failed: %d\n",
+ ns->nsid, ret);
+ goto free_st;
+ }
+
+ if (le16_to_cpu(st->nruhsd) < ns->fdp.nruh) {
+ pr_err("the ns:%u backing reports fewer handles than write streams\n",
+ ns->nsid);
+ ret = -EINVAL;
+ goto free_st;
+ }
+
+ for (i = 0; i < ns->fdp.nruh; i++) {
+ if (le16_to_cpu(st->ruhsd[i].pid) != i) {
+ pr_err("the ns:%u backing placement identifiers are not identity mapped\n",
+ ns->nsid);
+ ret = -EINVAL;
+ goto free_st;
+ }
+ }
+
+free_st:
+ kfree(st);
+free_id:
+ kfree(id);
+ return ret;
+}
+
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index e95bafe1fb019..037c3862062ab 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -103,6 +103,8 @@ struct nvmet_fdp {
u64 runs;
struct request_queue *backend_q;
struct nvme_ctrl *backend_ctrl;
+ u32 backend_nsid;
+ u16 backend_endgid;
};
struct nvmet_ns {
@@ -999,6 +1001,8 @@ void nvmet_fdp_exit_ns(struct nvmet_ns *ns);
bool nvmet_fdp_subsys_configured(struct nvmet_subsys *subsys);
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);
#else
static inline bool nvmet_fdp_enabled(struct nvmet_ns *ns)
{
@@ -1023,6 +1027,14 @@ static inline u16 nvmet_get_feat_fdp(struct nvmet_req *req)
{
return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
}
+static inline void nvmet_execute_io_mgmt_recv(struct nvmet_req *req)
+{
+ nvmet_req_complete(req, NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
+}
+static inline void nvmet_execute_io_mgmt_send(struct nvmet_req *req)
+{
+ nvmet_req_complete(req, NVME_SC_INVALID_FIELD | NVME_STATUS_DNR);
+}
#endif
/*
--
2.43.7
next prev parent reply other threads:[~2026-10-19 3:36 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 3:36 [PATCH RESEND 0/11] nvmet: FDP bridge for NVMe-oF targets Guixin Liu
2026-10-19 3:36 ` [PATCH 01/11] nvme: add the FDP wire structures for the I/O management send command Guixin Liu
2026-10-19 3:36 ` [PATCH 02/11] block: add bdev_write_stream_granularity() Guixin Liu
2026-10-19 3:36 ` [PATCH 03/11] nvme: export nvme_bdev_ops Guixin Liu
2026-10-19 3:36 ` [PATCH 04/11] nvmet: add per-namespace FDP bridge state and configuration Guixin Liu
2026-10-19 3:36 ` [PATCH 05/11] nvmet: report the Endurance Group Identifier in the CS-INDEP identify data Guixin Liu
2026-10-19 3:36 ` [PATCH 06/11] nvmet: advertise the FDP capability Guixin Liu
2026-10-19 3:36 ` [PATCH 07/11] nvmet: implement the FDP feature Guixin Liu
2026-10-19 3:45 ` sashiko-bot
2026-10-19 8:30 ` Guixin Liu
2026-10-19 3:36 ` Guixin Liu [this message]
2026-10-19 3:57 ` [PATCH 08/11] nvmet: forward the FDP I/O management commands to the backing device sashiko-bot
2026-10-19 9:44 ` Guixin Liu
2026-10-19 3:36 ` [PATCH 09/11] nvmet: serve the FDP log pages Guixin Liu
2026-10-19 4:07 ` sashiko-bot
2026-10-19 3:36 ` [PATCH 10/11] nvmet: translate placement identifiers of writes to write streams Guixin Liu
2026-10-19 3:44 ` sashiko-bot
2026-10-19 8:27 ` Guixin Liu
2026-10-19 3:36 ` [PATCH 11/11] nvmet: add an FDP write translation tracepoint Guixin Liu
2026-10-19 3:48 ` sashiko-bot
2026-10-19 8:32 ` Guixin Liu
-- strict thread matches above, loose matches on Subject: below --
2026-10-13 2:20 [PATCH 0/11] nvmet: FDP bridge for NVMe-oF targets Guixin Liu
2026-10-13 2:21 ` [PATCH 08/11] nvmet: forward the FDP I/O management commands to the backing device Guixin Liu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261019033624.402337-9-kanie@linux.alibaba.com \
--to=kanie@linux.alibaba.com \
--cc=axboe@kernel.dk \
--cc=dwagner@suse.de \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=kbusch@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=nilay@linux.ibm.com \
--cc=sagi@grimberg.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox