From 224041412693b0d60acee0573eee18cbcd281d64 Mon Sep 17 00:00:00 2001 From: Yao Sang Date: Wed, 10 Jun 2026 11:28:46 +0800 Subject: [PATCH 01/81] nvme-multipath: revalidate zones for namespace heads Zoned multipath namespace heads get BLK_FEAT_ZONED and their limits are refreshed from the paths, but the zone state for the head disk is never initialized. The previous nr_zones assignment only updated a single field and did not allocate or populate the block layer's per-zone state. The failure was found with xfstests xfs/643 and xfs/646 on an NVMe ZNS multipath namespace. Tracing showed regular REQ_OP_WRITE I/O being submitted to sequential zones through the multipath head. That leaves the head disk without valid zone condition information. Code using the head device, such as bdev_zone_is_seq(), can then treat a sequential zone as non-sequential and submit regular writes to it. Add a small helper to run blk_revalidate_disk_zones() for a live zoned namespace head after the path limits have been committed and when a path becomes live. Return the error to the namespace update path, and keep the live path transition as a warning-only update. Drop the nr_zones copy, as blk_revalidate_disk_zones() updates it together with the rest of the zoned disk state. Signed-off-by: Yao Sang Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 ++++ drivers/nvme/host/multipath.c | 24 ++++++++++++++++++++---- drivers/nvme/host/nvme.h | 9 +++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 453c1f0b2dd0..db0c8ad4628a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2592,11 +2592,15 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) lim.max_write_streams = ns_lim->max_write_streams; lim.write_stream_granularity = ns_lim->write_stream_granularity; ret = queue_limits_commit_update(ns->head->disk->queue, &lim); + if (ret) + goto unfreeze_head_queue; set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk)); set_disk_ro(ns->head->disk, nvme_ns_is_readonly(ns, info)); nvme_mpath_revalidate_paths(ns->head); + ret = nvme_mpath_revalidate_zones(ns->head); +unfreeze_head_queue: blk_mq_unfreeze_queue(ns->head->disk->queue, memflags); } diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 9b9a657fa330..7e9fb7227300 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -288,6 +288,25 @@ void nvme_mpath_revalidate_paths(struct nvme_ns_head *head) kblockd_schedule_work(&head->requeue_work); } +#ifdef CONFIG_BLK_DEV_ZONED +int nvme_mpath_revalidate_zones(struct nvme_ns_head *head) +{ + struct gendisk *disk = head->disk; + int ret; + + if (!disk || !blk_queue_is_zoned(disk->queue) || + !test_bit(NVME_NSHEAD_DISK_LIVE, &head->flags)) + return 0; + + ret = blk_revalidate_disk_zones(disk); + if (ret) + dev_warn_ratelimited(disk_to_dev(disk), + "failed to revalidate zoned namespace head: %d\n", + ret); + return ret; +} +#endif /* CONFIG_BLK_DEV_ZONED */ + static bool nvme_path_is_disabled(struct nvme_ns *ns) { enum nvme_ctrl_state state = nvme_ctrl_state(ns->ctrl); @@ -819,6 +838,7 @@ static void nvme_mpath_set_live(struct nvme_ns *ns) mutex_unlock(&head->lock); synchronize_srcu(&head->srcu); + nvme_mpath_revalidate_zones(head); kblockd_schedule_work(&head->requeue_work); } @@ -1375,10 +1395,6 @@ void nvme_mpath_add_disk(struct nvme_ns *ns, __le32 anagrpid) nvme_mpath_set_live(ns); } -#ifdef CONFIG_BLK_DEV_ZONED - if (blk_queue_is_zoned(ns->queue) && ns->head->disk) - ns->head->disk->nr_zones = ns->disk->nr_zones; -#endif } void nvme_mpath_remove_disk(struct nvme_ns_head *head) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 824651cc898d..a679a4c61462 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1184,6 +1184,15 @@ static inline bool nvme_mpath_queue_if_no_path(struct nvme_ns_head *head) } #endif /* CONFIG_NVME_MULTIPATH */ +#if defined(CONFIG_NVME_MULTIPATH) && defined(CONFIG_BLK_DEV_ZONED) +int nvme_mpath_revalidate_zones(struct nvme_ns_head *head); +#else +static inline int nvme_mpath_revalidate_zones(struct nvme_ns_head *head) +{ + return 0; +} +#endif + int nvme_ns_get_unique_id(struct nvme_ns *ns, u8 id[16], enum blk_unique_id type); From f61c934aa084b7440fec681be3f4b481eb5a8609 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Thu, 18 Jun 2026 10:15:43 +0800 Subject: [PATCH 02/81] nvme-apple: Use acquire/release for queue enabled state apple_nvme_init_queue() initializes queue state and then marks the queue enabled. The interrupt and request paths check enabled before using that queue state. The old wmb() after WRITE_ONCE(enabled, true) does not publish the earlier initialization before enabled becomes visible. Use a release store when enabling the queue and acquire loads when testing it. Although the shutdown-side enabled accesses are not used for publishing queue initialization, use helpers for them as well for consistency. Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Signed-off-by: Gui-Dong Han Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/apple.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index be3b91b43ea5..2723bc1a7d8a 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -151,6 +151,23 @@ struct apple_nvme_queue { bool enabled; }; +static inline bool apple_nvme_queue_enabled(struct apple_nvme_queue *q) +{ + /* Pair with apple_nvme_enable_queue(). */ + return smp_load_acquire(&q->enabled); +} + +static inline void apple_nvme_enable_queue(struct apple_nvme_queue *q) +{ + /* Publish queue initialization before setting q->enabled. */ + smp_store_release(&q->enabled, true); +} + +static inline void apple_nvme_disable_queue(struct apple_nvme_queue *q) +{ + WRITE_ONCE(q->enabled, false); +} + /* * The apple_nvme_iod describes the data in an I/O. * @@ -677,7 +694,7 @@ static bool apple_nvme_handle_cq(struct apple_nvme_queue *q, bool force) bool found; DEFINE_IO_COMP_BATCH(iob); - if (!READ_ONCE(q->enabled) && !force) + if (!apple_nvme_queue_enabled(q) && !force) return false; found = apple_nvme_poll_cq(q, &iob); @@ -780,7 +797,7 @@ static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx, * We should not need to do this, but we're still using this to * ensure we can drain requests on a dying queue. */ - if (unlikely(!READ_ONCE(q->enabled))) + if (unlikely(!apple_nvme_queue_enabled(q))) return BLK_STS_IOERR; if (!nvme_check_ready(&anv->ctrl, req, true)) @@ -863,7 +880,7 @@ static void apple_nvme_disable(struct apple_nvme *anv, bool shutdown) nvme_quiesce_io_queues(&anv->ctrl); if (!dead) { - if (READ_ONCE(anv->ioq.enabled)) { + if (apple_nvme_queue_enabled(&anv->ioq)) { apple_nvme_remove_sq(anv); apple_nvme_remove_cq(anv); } @@ -887,8 +904,8 @@ static void apple_nvme_disable(struct apple_nvme *anv, bool shutdown) nvme_disable_ctrl(&anv->ctrl, false); } - WRITE_ONCE(anv->ioq.enabled, false); - WRITE_ONCE(anv->adminq.enabled, false); + apple_nvme_disable_queue(&anv->ioq); + apple_nvme_disable_queue(&anv->adminq); mb(); /* ensure that nvme_queue_rq() sees that enabled is cleared */ nvme_quiesce_admin_queue(&anv->ctrl); @@ -1016,8 +1033,7 @@ static void apple_nvme_init_queue(struct apple_nvme_queue *q) memset(q->tcbs, 0, anv->hw->max_queue_depth * sizeof(struct apple_nvmmu_tcb)); memset(q->cqes, 0, depth * sizeof(struct nvme_completion)); - WRITE_ONCE(q->enabled, true); - wmb(); /* ensure the first interrupt sees the initialization */ + apple_nvme_enable_queue(q); } static void apple_nvme_reset_work(struct work_struct *work) From 4fe024eeba34b87b7e7388139d7836cde9928c6a Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Mon, 22 Jun 2026 11:32:54 +0800 Subject: [PATCH 03/81] nvme: fix typos in reservation related constants Fix the following spelling errors: - NVMET_PR_NOTIFI_MASK_ALL -> NVMET_PR_NOTIFY_MASK_ALL - NVME_PR_LOG_RESERVATOIN_PREEMPTED -> NVME_PR_LOG_RESERVATION_PREEMPTED - NVME_AEN_RESV_LOG_PAGE_AVALIABLE -> NVME_AEN_RESV_LOG_PAGE_AVAILABLE Signed-off-by: Guixin Liu Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/pr.c | 10 +++++----- include/linux/nvme.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index c71ae46244ff..5dd2f3553d8c 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -8,7 +8,7 @@ #include #include "nvmet.h" -#define NVMET_PR_NOTIFI_MASK_ALL \ +#define NVMET_PR_NOTIFY_MASK_ALL \ (1 << NVME_PR_NOTIFY_BIT_REG_PREEMPTED | \ 1 << NVME_PR_NOTIFY_BIT_RESV_RELEASED | \ 1 << NVME_PR_NOTIFY_BIT_RESV_PREEMPTED) @@ -44,7 +44,7 @@ u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask) unsigned long idx; u16 status; - if (mask & ~(NVMET_PR_NOTIFI_MASK_ALL)) { + if (mask & ~(NVMET_PR_NOTIFY_MASK_ALL)) { req->error_loc = offsetof(struct nvme_common_command, cdw11); return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; } @@ -169,7 +169,7 @@ static void nvmet_pr_resv_released(struct nvmet_pr *pr, uuid_t *hostid) nvmet_pr_add_resv_log(ctrl, NVME_PR_LOG_RESERVATION_RELEASED, ns->nsid); nvmet_add_async_event(ctrl, NVME_AER_CSS, - NVME_AEN_RESV_LOG_PAGE_AVALIABLE, + NVME_AEN_RESV_LOG_PAGE_AVAILABLE, NVME_LOG_RESERVATION); } } @@ -188,7 +188,7 @@ static void nvmet_pr_send_event_to_host(struct nvmet_pr *pr, uuid_t *hostid, if (uuid_equal(hostid, &ctrl->hostid)) { nvmet_pr_add_resv_log(ctrl, log_type, ns->nsid); nvmet_add_async_event(ctrl, NVME_AER_CSS, - NVME_AEN_RESV_LOG_PAGE_AVALIABLE, + NVME_AEN_RESV_LOG_PAGE_AVAILABLE, NVME_LOG_RESERVATION); } } @@ -201,7 +201,7 @@ static void nvmet_pr_resv_preempted(struct nvmet_pr *pr, uuid_t *hostid) return; nvmet_pr_send_event_to_host(pr, hostid, - NVME_PR_LOG_RESERVATOIN_PREEMPTED); + NVME_PR_LOG_RESERVATION_PREEMPTED); } static void nvmet_pr_registration_preempted(struct nvmet_pr *pr, diff --git a/include/linux/nvme.h b/include/linux/nvme.h index 041f30931a90..91ce434a7e8d 100644 --- a/include/linux/nvme.h +++ b/include/linux/nvme.h @@ -2272,14 +2272,14 @@ struct nvme_completion { #define NVME_TERTIARY(ver) ((ver) & 0xff) enum { - NVME_AEN_RESV_LOG_PAGE_AVALIABLE = 0x00, + NVME_AEN_RESV_LOG_PAGE_AVAILABLE = 0x00, }; enum { NVME_PR_LOG_EMPTY_LOG_PAGE = 0x00, NVME_PR_LOG_REGISTRATION_PREEMPTED = 0x01, NVME_PR_LOG_RESERVATION_RELEASED = 0x02, - NVME_PR_LOG_RESERVATOIN_PREEMPTED = 0x03, + NVME_PR_LOG_RESERVATION_PREEMPTED = 0x03, }; enum { From 09c9062d4f62199a634a45e2bb9e6e8e572cc78c Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Thu, 25 Jun 2026 10:00:00 +0800 Subject: [PATCH 04/81] nvme: zns: cap zone report nr_zones by DMA buffer size With Partial Report (PR=1), the Number of Zones (NZ) field in the report header must equal the number of zone descriptors fully transferred in the DMA buffer (ZNS Command Set Specification Rev 1.2, section 3.4.2). nvme_ns_report_zones() does not cap the parse loop by max_in_buf derived from buflen. Cap nz with min3() over the device-reported count, nr_zones - zone_idx, and max_in_buf. Signed-off-by: Xixin Liu Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/zns.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/zns.c b/drivers/nvme/host/zns.c index 8ed1b6a33454..03a2528b7192 100644 --- a/drivers/nvme/host/zns.c +++ b/drivers/nvme/host/zns.c @@ -178,7 +178,7 @@ int nvme_ns_report_zones(struct nvme_ns *ns, sector_t sector, struct nvme_zone_report *report; struct nvme_command c = { }; int ret, zone_idx = 0; - unsigned int nz, i; + unsigned int max_in_buf, nz, i; size_t buflen; if (ns->head->ids.csi != NVME_CSI_ZNS) @@ -188,6 +188,9 @@ int nvme_ns_report_zones(struct nvme_ns *ns, sector_t sector, if (!report) return -ENOMEM; + max_in_buf = (buflen - sizeof(struct nvme_zone_report)) / + sizeof(struct nvme_zone_descriptor); + c.zmr.opcode = nvme_cmd_zone_mgmt_recv; c.zmr.nsid = cpu_to_le32(ns->head->ns_id); c.zmr.numd = cpu_to_le32(nvme_bytes_to_numd(buflen)); @@ -207,7 +210,8 @@ int nvme_ns_report_zones(struct nvme_ns *ns, sector_t sector, goto out_free; } - nz = min((unsigned int)le64_to_cpu(report->nr_zones), nr_zones); + nz = min3((unsigned int)le64_to_cpu(report->nr_zones), + nr_zones - zone_idx, max_in_buf); if (!nz) break; From 3456b525528967456a8837b5dc166507d877a476 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Thu, 25 Jun 2026 10:00:00 +0800 Subject: [PATCH 05/81] nvme: zns: include zone index in invalid zone type error Include the zone index when reporting an invalid zone type during zone descriptor parsing. Signed-off-by: Xixin Liu Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/zns.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/zns.c b/drivers/nvme/host/zns.c index 03a2528b7192..2a152e87bd76 100644 --- a/drivers/nvme/host/zns.c +++ b/drivers/nvme/host/zns.c @@ -155,7 +155,8 @@ static int nvme_zone_parse_entry(struct nvme_ns *ns, struct blk_zone zone = { }; if ((entry->zt & 0xf) != NVME_ZONE_TYPE_SEQWRITE_REQ) { - dev_err(ns->ctrl->device, "invalid zone type %#x\n", entry->zt); + dev_err(ns->ctrl->device, "invalid zone type %#x at zone %u\n", + entry->zt, idx); return -EINVAL; } From ba6e9472b453ccea313c7dad5bf2ad98d6ad13f5 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 25 Jun 2026 10:59:11 -0700 Subject: [PATCH 06/81] nvme-auth: Avoid C=1 warning in nvme_auth_derive_tls_psk() The following works fine with gcc and clang, but sparse warns about label_len not being an actual constant expression: const size_t label_len = sizeof(label) - 1; ... static_assert(label_len <= 255); Avoid this by giving label an explicit length and using sizeof(label) instead of label_len. Reported-by: John Garry Closes: https://lore.kernel.org/linux-nvme/965a37dd-f698-46b6-9623-1099a13f7e60@oracle.com Fixes: d126cbaa7d9a ("nvme-auth: common: use crypto library in nvme_auth_derive_tls_psk()") Signed-off-by: Eric Biggers Reviewed-by: Hannes Reinecke Reviewed-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/common/auth.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c index 77f1d22512f8..e2e0c736540a 100644 --- a/drivers/nvme/common/auth.c +++ b/drivers/nvme/common/auth.c @@ -692,8 +692,7 @@ int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len, const char *psk_digest, u8 **ret_psk) { static const u8 default_salt[NVME_AUTH_MAX_DIGEST_SIZE]; - static const char label[] = "tls13 nvme-tls-psk"; - const size_t label_len = sizeof(label) - 1; + static const char label[18] = "tls13 nvme-tls-psk"; u8 prk[NVME_AUTH_MAX_DIGEST_SIZE]; size_t hash_len, ctx_len; u8 *hmac_data = NULL, *tls_key; @@ -729,7 +728,7 @@ int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len, */ hmac_data = kmalloc(/* output length */ 2 + - /* label */ 1 + label_len + + /* label */ 1 + sizeof(label) + /* context (max) */ 1 + 3 + 1 + strlen(psk_digest) + /* counter */ 1, GFP_KERNEL); @@ -743,10 +742,10 @@ int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len, hmac_data[i++] = hash_len; /* label */ - static_assert(label_len <= 255); - hmac_data[i] = label_len; - memcpy(&hmac_data[i + 1], label, label_len); - i += 1 + label_len; + static_assert(sizeof(label) <= 255); + hmac_data[i] = sizeof(label); + memcpy(&hmac_data[i + 1], label, sizeof(label)); + i += 1 + sizeof(label); /* context */ ctx_len = sprintf(&hmac_data[i + 1], "%02d %s", hmac_id, psk_digest); From f4254b18d48af66a99101092b1d72f58c4c69b23 Mon Sep 17 00:00:00 2001 From: Surabhi Gogte Date: Fri, 26 Jun 2026 22:15:50 -0600 Subject: [PATCH 07/81] nvme-rdma: refactor nvme_rdma_alloc_queue() to take a queue pointer Callers are responsible for initializing queue->ctrl and queue->queue_size before calling nvme_rdma_alloc_queue(), which now derives ctrl and idx from the queue pointer directly. This removes redundant assignments inside the function and simplifies the interface. Signed-off-by: Surabhi Gogte Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 6909e3542794..6b0b0a3dea62 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -566,16 +566,14 @@ static int nvme_rdma_create_queue_ib(struct nvme_rdma_queue *queue) return ret; } -static int nvme_rdma_alloc_queue(struct nvme_rdma_ctrl *ctrl, - int idx, size_t queue_size) +static int nvme_rdma_alloc_queue(struct nvme_rdma_queue *queue) { - struct nvme_rdma_queue *queue; + struct nvme_rdma_ctrl *ctrl = queue->ctrl; + int idx = nvme_rdma_queue_idx(queue); struct sockaddr *src_addr = NULL; int ret; - queue = &ctrl->queues[idx]; mutex_init(&queue->queue_lock); - queue->ctrl = ctrl; if (idx && ctrl->ctrl.max_integrity_segments) queue->pi_support = true; else @@ -587,8 +585,6 @@ static int nvme_rdma_alloc_queue(struct nvme_rdma_ctrl *ctrl, else queue->cmnd_capsule_len = sizeof(struct nvme_command); - queue->queue_size = queue_size; - queue->cm_id = rdma_create_id(&init_net, nvme_rdma_cm_handler, queue, RDMA_PS_TCP, IB_QPT_RC); if (IS_ERR(queue->cm_id)) { @@ -736,8 +732,9 @@ static int nvme_rdma_alloc_io_queues(struct nvme_rdma_ctrl *ctrl) nvmf_set_io_queues(opts, nr_io_queues, ctrl->io_queues); for (i = 1; i < ctrl->ctrl.queue_count; i++) { - ret = nvme_rdma_alloc_queue(ctrl, i, - ctrl->ctrl.sqsize + 1); + ctrl->queues[i].ctrl = ctrl; + ctrl->queues[i].queue_size = ctrl->ctrl.sqsize + 1; + ret = nvme_rdma_alloc_queue(&ctrl->queues[i]); if (ret) goto out_free_queues; } @@ -783,7 +780,9 @@ static int nvme_rdma_configure_admin_queue(struct nvme_rdma_ctrl *ctrl, bool pi_capable = false; int error; - error = nvme_rdma_alloc_queue(ctrl, 0, NVME_AQ_DEPTH); + ctrl->queues[0].ctrl = ctrl; + ctrl->queues[0].queue_size = NVME_AQ_DEPTH; + error = nvme_rdma_alloc_queue(&ctrl->queues[0]); if (error) return error; From 2a8513091d2f0b9a1e94b1843c48c712e7c17301 Mon Sep 17 00:00:00 2001 From: Surabhi Gogte Date: Fri, 26 Jun 2026 22:15:51 -0600 Subject: [PATCH 08/81] nvme-rdma: parallelize I/O queue allocation and startup Refactor nvme rdma I/O queue setup to use async API, combining allocation and startup into a single parallel operation per queue. This reduces connection and reconnection setup time when there are delays in establishing connections, which is especially important for high-core-count hosts. Key changes: - Use async API to facilitate parallel calls for io queue setup. - Add nvme_rdma_setup_ctx for propagating errors from async workers. - Remove nvme_rdma_alloc_io_queues() and nvme_rdma_start_io_queues(); their logic is folded into nvme_rdma_setup_io_queues() and nvme_rdma_configure_io_queues(). - Move queue count negotiation (nvme_set_queue_count, nvmf_set_io_queues) from the removed nvme_rdma_alloc_io_queues() into nvme_rdma_configure_io_queues(). Testing on a 64-core host with 64 IO-queues shows nvme-rdma connection time reduced from ~1.4s to 416ms. Signed-off-by: Surabhi Gogte Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 122 ++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 6b0b0a3dea62..52933d11ea03 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -100,6 +101,11 @@ struct nvme_rdma_queue { struct mutex queue_lock; }; +struct nvme_rdma_setup_ctx { + struct nvme_rdma_queue *queue; + int *err; +}; + struct nvme_rdma_ctrl { /* read only in the hot path */ struct nvme_rdma_queue *queues; @@ -690,60 +696,68 @@ static int nvme_rdma_start_queue(struct nvme_rdma_ctrl *ctrl, int idx) return ret; } -static int nvme_rdma_start_io_queues(struct nvme_rdma_ctrl *ctrl, - int first, int last) +static void nvme_rdma_setup_queue_async(void *data, async_cookie_t cookie) { - int i, ret = 0; + struct nvme_rdma_setup_ctx *ctx = data; + struct nvme_rdma_queue *queue; + int ret; - for (i = first; i < last; i++) { - ret = nvme_rdma_start_queue(ctrl, i); - if (ret) - goto out_stop_queues; - } + queue = ctx->queue; + ret = nvme_rdma_alloc_queue(queue); + if (ret) + goto out_err; - return 0; + ret = nvme_rdma_start_queue(queue->ctrl, nvme_rdma_queue_idx(queue)); + if (ret) + goto out_err; -out_stop_queues: - for (i--; i >= first; i--) - nvme_rdma_stop_queue(&ctrl->queues[i]); - return ret; + return; +out_err: + WRITE_ONCE(*ctx->err, ret); } -static int nvme_rdma_alloc_io_queues(struct nvme_rdma_ctrl *ctrl) +static int nvme_rdma_setup_io_queues(struct nvme_rdma_ctrl *ctrl, + unsigned int first, unsigned int last, size_t queue_size) { - struct nvmf_ctrl_options *opts = ctrl->ctrl.opts; - unsigned int nr_io_queues; - int i, ret; + ASYNC_DOMAIN_EXCLUSIVE(queue_domain); + struct nvme_rdma_setup_ctx *ctxs; + int nr_queues = last - first; + int err = 0, i, ret; - nr_io_queues = nvmf_nr_io_queues(opts); - ret = nvme_set_queue_count(&ctrl->ctrl, &nr_io_queues); - if (ret) - return ret; - - if (nr_io_queues == 0) { - dev_err(ctrl->ctrl.device, - "unable to set any I/O queues\n"); + ctxs = kmalloc_objs(*ctxs, nr_queues); + if (!ctxs) return -ENOMEM; + + for (i = 0; i < nr_queues; i++) { + struct nvme_rdma_queue *queue = &ctrl->queues[first + i]; + + queue->ctrl = ctrl; + queue->queue_size = queue_size; + + ctxs[i].queue = queue; + ctxs[i].err = &err; + async_schedule_domain(nvme_rdma_setup_queue_async, &ctxs[i], + &queue_domain); } - ctrl->ctrl.queue_count = nr_io_queues + 1; - dev_info(ctrl->ctrl.device, - "creating %d I/O queues.\n", nr_io_queues); + async_synchronize_full_domain(&queue_domain); + kfree(ctxs); - nvmf_set_io_queues(opts, nr_io_queues, ctrl->io_queues); - for (i = 1; i < ctrl->ctrl.queue_count; i++) { - ctrl->queues[i].ctrl = ctrl; - ctrl->queues[i].queue_size = ctrl->ctrl.sqsize + 1; - ret = nvme_rdma_alloc_queue(&ctrl->queues[i]); - if (ret) - goto out_free_queues; - } + ret = READ_ONCE(err); + if (ret) + goto out_free_queues; return 0; - out_free_queues: - for (i--; i >= 1; i--) - nvme_rdma_free_queue(&ctrl->queues[i]); + for (i = 0; i < nr_queues; i++) { + struct nvme_rdma_queue *queue = + &ctrl->queues[first + i]; + + if (test_bit(NVME_RDMA_Q_LIVE, &queue->flags)) + nvme_rdma_stop_queue(queue); + if (test_bit(NVME_RDMA_Q_ALLOCATED, &queue->flags)) + nvme_rdma_free_queue(queue); + } return ret; } @@ -862,12 +876,23 @@ static int nvme_rdma_configure_admin_queue(struct nvme_rdma_ctrl *ctrl, static int nvme_rdma_configure_io_queues(struct nvme_rdma_ctrl *ctrl, bool new) { + unsigned int nr_io_queues; int ret, nr_queues; - ret = nvme_rdma_alloc_io_queues(ctrl); + nr_io_queues = nvmf_nr_io_queues(ctrl->ctrl.opts); + ret = nvme_set_queue_count(&ctrl->ctrl, &nr_io_queues); if (ret) return ret; + if (nr_io_queues == 0) { + dev_err(ctrl->ctrl.device, "unable to set any I/O queues\n"); + return -ENOMEM; + } + + ctrl->ctrl.queue_count = nr_io_queues + 1; + dev_info(ctrl->ctrl.device, "creating %d I/O queues.\n", nr_io_queues); + nvmf_set_io_queues(ctrl->ctrl.opts, nr_io_queues, ctrl->io_queues); + if (new) { ret = nvme_rdma_alloc_tag_set(&ctrl->ctrl); if (ret) @@ -880,7 +905,9 @@ static int nvme_rdma_configure_io_queues(struct nvme_rdma_ctrl *ctrl, bool new) * queue number might have changed. */ nr_queues = min(ctrl->tag_set.nr_hw_queues + 1, ctrl->ctrl.queue_count); - ret = nvme_rdma_start_io_queues(ctrl, 1, nr_queues); + ret = nvme_rdma_setup_io_queues(ctrl, 1, nr_queues, + ctrl->ctrl.sqsize + 1); + if (ret) goto out_cleanup_tagset; @@ -904,12 +931,15 @@ static int nvme_rdma_configure_io_queues(struct nvme_rdma_ctrl *ctrl, bool new) /* * If the number of queues has increased (reconnect case) - * start all new queues now. + * setup all new queues now. */ - ret = nvme_rdma_start_io_queues(ctrl, nr_queues, - ctrl->tag_set.nr_hw_queues + 1); - if (ret) - goto out_wait_freeze_timed_out; + if (ctrl->tag_set.nr_hw_queues + 1 > nr_queues) { + ret = nvme_rdma_setup_io_queues(ctrl, nr_queues, + ctrl->tag_set.nr_hw_queues + 1, + ctrl->ctrl.sqsize + 1); + if (ret) + goto out_wait_freeze_timed_out; + } return 0; From 90096175473f7c86e39c3f74f10343f965f5a05d Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 29 Jun 2026 14:15:27 +0900 Subject: [PATCH 09/81] nvmet-rdma: factor out response resource cleanup Move the RDMA read/write context teardown and the request SGL freeing out of nvmet_rdma_release_rsp() into a new helper function nvmet_rdma_free_rsp_resources(). This is a refactoring with no functional change, in preparation for the following patch that uses nvmet_rdma_free_rsp_resources(). Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index ea1185b8267e..3c34f235e542 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -657,18 +657,25 @@ static void nvmet_rdma_rw_ctx_destroy(struct nvmet_rdma_rsp *rsp) req->sg, req->sg_cnt, nvmet_data_dir(req)); } -static void nvmet_rdma_release_rsp(struct nvmet_rdma_rsp *rsp) +static void nvmet_rdma_free_rsp_resources(struct nvmet_rdma_rsp *rsp) { struct nvmet_rdma_queue *queue = rsp->queue; - atomic_add(1 + rsp->n_rdma, &queue->sq_wr_avail); - if (rsp->n_rdma) nvmet_rdma_rw_ctx_destroy(rsp); if (rsp->req.sg < rsp->cmd->inline_sg || rsp->req.sg >= rsp->cmd->inline_sg + queue->dev->inline_page_count) nvmet_req_free_sgls(&rsp->req); +} + +static void nvmet_rdma_release_rsp(struct nvmet_rdma_rsp *rsp) +{ + struct nvmet_rdma_queue *queue = rsp->queue; + + atomic_add(1 + rsp->n_rdma, &queue->sq_wr_avail); + + nvmet_rdma_free_rsp_resources(rsp); if (unlikely(!list_empty_careful(&queue->rsp_wr_wait_list))) nvmet_rdma_process_wr_wait_list(queue); From 0114dd303b373522dea06053aabae34bdd33a7c4 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 29 Jun 2026 14:15:28 +0900 Subject: [PATCH 10/81] nvmet-rdma: fix response resource leak on queue teardown When an nvme target with rdma transport is removed while I/Os are in flight, a response can be posted but its send completion is never delivered before the connection is torn down. As a result nvmet_rdma_send_done() and nvmet_rdma_release_rsp() are never called for the response, and this leaks the allocated RDMA read/write context and request SGLs. These leaks are recreated by running blktests nvme/061 with the rdma transport and the siw driver. Kernel kmemleak feature reports them as follows: unreferenced object 0xffff88812bc490c0 (size 32): comm "kworker/2:1H", pid 409, jiffies 4307744490 backtrace (crc 89afd339): __kmalloc_noprof+0x5f9/0x890 sgl_alloc_order+0x7b/0x380 nvmet_req_alloc_sgls+0x290/0x4f0 [nvmet] nvmet_rdma_map_sgl_keyed+0x241/0x12e0 [nvmet_rdma] nvmet_rdma_handle_command+0x73e/0xb80 [nvmet_rdma] __ib_process_cq+0x149/0x4c0 [ib_core] ib_cq_poll_work+0x49/0x160 [ib_core] process_one_work+0x8b2/0x1640 worker_thread+0x5fd/0xfe0 kthread+0x367/0x460 ret_from_fork+0x655/0x9d0 ret_from_fork_asm+0x1a/0x30 unreferenced object 0xffff88814bd05e80 (size 64): comm "kworker/3:1H", pid 148, jiffies 4295195428 backtrace (crc e35510cb): __kmalloc_noprof+0x5f9/0x890 rdma_rw_ctx_init+0x333/0x1fa0 [ib_core] nvmet_rdma_map_sgl_keyed+0x5c8/0x12e0 [nvmet_rdma] nvmet_rdma_handle_command+0x73e/0xb80 [nvmet_rdma] __ib_process_cq+0x149/0x4c0 [ib_core] ib_cq_poll_work+0x49/0x160 [ib_core] process_one_work+0x8b2/0x1640 worker_thread+0x5fd/0xfe0 kthread+0x367/0x460 ret_from_fork+0x655/0x9d0 ret_from_fork_asm+0x1a/0x30 To avoid the memory leaks, reclaim the memory of the in-flight responses when the queue QP is torn down. Call nvmet_rdma_free_rsp_resources() that frees up the RDMA read/write context and the request SGLs of such responses. Fixes: 8f000cac6e7a ("nvmet-rdma: add a NVMe over Fabrics RDMA target driver") Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index 3c34f235e542..de5a88fbb233 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -1345,9 +1345,27 @@ static int nvmet_rdma_create_queue_ib(struct nvmet_rdma_queue *queue) goto out; } +static bool nvmet_rdma_reclaim_rsp(struct sbitmap *sb, unsigned int bitnr, + void *data) +{ + struct nvmet_rdma_queue *queue = data; + + nvmet_rdma_free_rsp_resources(&queue->rsps[bitnr]); + + return true; +} + static void nvmet_rdma_destroy_queue_ib(struct nvmet_rdma_queue *queue) { ib_drain_qp(queue->qp); + + /* + * Reclaim resources of a response that is still in-flight when the + * queue is being torn down. This happens when the connection was + * forcefully disconnected while an I/O is in flight. + */ + sbitmap_for_each_set(&queue->rsp_tags, nvmet_rdma_reclaim_rsp, queue); + if (queue->cm_id) rdma_destroy_id(queue->cm_id); ib_destroy_qp(queue->qp); From dd516cd7624648c71d338d99368587f10ecc9f0b Mon Sep 17 00:00:00 2001 From: John Garry Date: Tue, 30 Jun 2026 10:27:17 +0000 Subject: [PATCH 11/81] nvme: handle positive error codes in nuse_show() Function __nvme_submit_sync_cmd() returns a positive error code for NVMe errors. Otherwise, we get 0 for success or a negative error code for a kernel error. In nuse_show() -> ns_{head}_update_nuse() -> nvme_identify_ns() -> nvme_submit_sync_cmd() -> __nvme_submit_sync_cmd(), we then may get a positive error code returned. Function nuse_show() - being a device attr handler - should return the number of bytes written to the buffer or a negative error code. Convert any positive NVMe error code to -EIO. Signed-off-by: John Garry Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 75b2d69b5957..abf8edaae371 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -240,8 +240,10 @@ static ssize_t nuse_show(struct device *dev, struct device_attribute *attr, ret = ns_head_update_nuse(head); else ret = ns_update_nuse(disk->private_data); - if (ret) + if (ret < 0) return ret; + else if (ret > 0) + return -EIO; return sysfs_emit(buf, "%llu\n", head->nuse); } From 3a6d89836ab09d53f4c32c57588689a378ce4363 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Wed, 1 Jul 2026 14:30:00 +0800 Subject: [PATCH 12/81] nvme-auth: use crypto_memneq for DH-HMAC-CHAP response comparison DH-HMAC-CHAP authentication compares HMAC response digests with memcmp(). Standard memcmp() may stop at the first differing byte, which can leak timing information to a remote attacker and allow incremental recovery of the expected digest. Use crypto_memneq() for constant-time comparison on both the host path that validates the controller Success1 response and the target path that validates the host Reply digest. Other memcmp() uses in the NVMe auth code (e.g. fixed string prefix checks) are not security-sensitive and are left unchanged. Signed-off-by: Xixin Liu Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/auth.c | 3 ++- drivers/nvme/target/fabrics-cmd-auth.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c index 16de4499a8e7..e55920642f2c 100644 --- a/drivers/nvme/host/auth.c +++ b/drivers/nvme/host/auth.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "nvme.h" #include "fabrics.h" #include @@ -361,7 +362,7 @@ static int nvme_auth_process_dhchap_success1(struct nvme_ctrl *ctrl, return 0; /* Validate controller response */ - if (memcmp(chap->response, data->rval, data->hl)) { + if (crypto_memneq(chap->response, data->rval, data->hl)) { dev_dbg(ctrl->device, "%s: qid %d ctrl response %*ph\n", __func__, chap->qid, (int)chap->hash_len, data->rval); dev_dbg(ctrl->device, "%s: qid %d host response %*ph\n", diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 45820a12750d..03529e19698b 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "nvmet.h" static void nvmet_auth_expired_work(struct work_struct *work) @@ -177,7 +178,7 @@ static u8 nvmet_auth_reply(struct nvmet_req *req, void *d, u32 tl) return NVME_AUTH_DHCHAP_FAILURE_FAILED; } - if (memcmp(data->rval, response, data->hl)) { + if (crypto_memneq(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", From 3ddcfb013322aa37eaa7a0d344b73079c38dfa21 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Thu, 2 Jul 2026 03:45:14 -0500 Subject: [PATCH 13/81] nvmet-auth: zero the AUTH_RECEIVE response buffer nvmet_execute_auth_receive() allocates the response buffer with kmalloc() sized by the host-supplied AUTH_RECEIVE allocation length, but the DH-HMAC-CHAP builders write only a fixed-size message into it. The full allocation length is then copied to the wire by nvmet_copy_to_sgl(), so a remote initiator receives the bytes past the built message -- up to nearly a page of uninitialized slab -- during the pre-authentication handshake. Allocate the buffer with kzalloc() so the unwritten tail is zeroed before it is sent; conforming responses are unaffected. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 03529e19698b..d1b39e64d877 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -558,7 +558,7 @@ void nvmet_execute_auth_receive(struct nvmet_req *req) return; } - d = kmalloc(al, GFP_KERNEL); + d = kzalloc(al, GFP_KERNEL); if (!d) { status = NVME_SC_INTERNAL; goto done; From 627e8bb91a8fdec02cfb68a98d360cec4bafa122 Mon Sep 17 00:00:00 2001 From: John Garry Date: Tue, 7 Jul 2026 14:57:44 +0100 Subject: [PATCH 14/81] nvme: swap synchronization ordering in nvme_remove_head() sashiko bot reported a potential issue in the requeue handling in [0] - the code there is same as the NVMe driver. The issue is that when we schedule the requeue work, if a bio is added to the requeue list afterwards in nvme_ns_head_submit_bio(), it is missed by the requeue worker. This issue can be recreated by hacking a large delay in the bio submission requeue path: } else if (nvme_available_path(head)) { dev_warn_ratelimited(dev, "no usable path - requeuing I/O\n"); + msleep(30000); spin_lock_irq(&head->requeue_lock); bio_list_add(&head->requeue_list, bio); spin_unlock_irq(&head->requeue_lock); Then if we issue a write after removing all paths, a hang can be seen: # echo 20 > /sys/devices/virtual/nvme-subsystem/nvme-subsys1/nvme1n1/delayed_removal_secs # # ./ini_nvme_teardown.sh [ 25.877224] nvme nvme1: Removing ctrl: NQN "nvme-test-target" [ 25.939569] nvme nvme2: Removing ctrl: NQN "nvme-test-target" # # xfs_io -d -C "pwrite -b 64k -V 1 -D 0 64k" /dev/nvme1n1p1 [ 29.883653] block nvme1n1: no usable path - requeuing I/O Fix by re-ordering the SRCU synchronization and scheduling the requeue work. [0] https://lore.kernel.org/linux-scsi/20260703102918.3723667-1-john.g.garry@oracle.com/T/#m72af1f29deb0ebfb2973464207f201f1be1f660c Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 7e9fb7227300..56587ae59c7f 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -711,14 +711,15 @@ static void nvme_remove_head(struct nvme_ns_head *head) { if (test_and_clear_bit(NVME_NSHEAD_DISK_LIVE, &head->flags)) { /* - * requeue I/O after NVME_NSHEAD_DISK_LIVE has been cleared - * to allow multipath to fail all I/O. + * Requeue I/O after NVME_NSHEAD_DISK_LIVE has been cleared + * to allow multipath to fail all I/O. First synchronize to + * add any bios to the requeue list. */ + synchronize_srcu(&head->srcu); kblockd_schedule_work(&head->requeue_work); if (test_and_clear_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags)) nvme_cdev_del(&head->cdev, &head->cdev_device); - synchronize_srcu(&head->srcu); del_gendisk(head->disk); } nvme_put_ns_head(head); From 77c57daf98a2ad95ec9ac1371caeff8939cc1f58 Mon Sep 17 00:00:00 2001 From: John Garry Date: Mon, 6 Jul 2026 12:54:02 +0000 Subject: [PATCH 15/81] nvme: don't reference NS after unlocking in nvme_ns_head_ctrl_ioctl() In nvme_ns_head_ctrl_ioctl(), once we drop the SRCU read lock we should not reference the NS to lookup the controller, so use the available controller pointer directly. Reviewed-by: Christoph Hellwig Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/ioctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c index 664216eece4a..d5a8f375953b 100644 --- a/drivers/nvme/host/ioctl.c +++ b/drivers/nvme/host/ioctl.c @@ -699,7 +699,7 @@ static int nvme_ns_head_ctrl_ioctl(struct nvme_ns *ns, unsigned int cmd, nvme_get_ctrl(ns->ctrl); srcu_read_unlock(&head->srcu, srcu_idx); - ret = nvme_ctrl_ioctl(ns->ctrl, cmd, argp, open_for_write); + ret = nvme_ctrl_ioctl(ctrl, cmd, argp, open_for_write); nvme_put_ctrl(ctrl); return ret; From 8f82aaf16f1c620558c3e0f3a76528c6787835bc Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 7 Jul 2026 20:11:13 +0800 Subject: [PATCH 16/81] nvmet: add namespace-level debugfs directory Add per-namespace debugfs directory support under the subsystem debugfs directory. Each enabled namespace gets a ns/ directory created during nvmet_ns_enable() and removed during nvmet_ns_disable(). This provides the infrastructure for exposing namespace-specific debug information in subsequent patches. Reviewed-by: Daniel Wagner Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 2 ++ drivers/nvme/target/debugfs.c | 21 +++++++++++++++++++++ drivers/nvme/target/debugfs.h | 5 +++++ drivers/nvme/target/nvmet.h | 3 +++ 4 files changed, 31 insertions(+) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 4477c4d6b1ee..a2403a808360 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -616,6 +616,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns) nvmet_ns_changed(subsys, ns->nsid); ns->enabled = true; xa_set_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); + nvmet_debugfs_ns_setup(ns); ret = 0; out_unlock: mutex_unlock(&subsys->lock); @@ -642,6 +643,7 @@ void nvmet_ns_disable(struct nvmet_ns *ns) ns->enabled = false; xa_clear_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); + nvmet_debugfs_ns_free(ns); list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) pci_dev_put(radix_tree_delete(&ctrl->p2p_ns_map, ns->nsid)); diff --git a/drivers/nvme/target/debugfs.c b/drivers/nvme/target/debugfs.c index 5dcbd5aa86e1..e6f51eb59010 100644 --- a/drivers/nvme/target/debugfs.c +++ b/drivers/nvme/target/debugfs.c @@ -153,6 +153,27 @@ static int nvmet_ctrl_tls_concat_show(struct seq_file *m, void *p) NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_concat); #endif +void nvmet_debugfs_ns_setup(struct nvmet_ns *ns) +{ + char name[16]; + struct dentry *parent = ns->subsys->debugfs_dir; + + if (!parent) + return; + snprintf(name, sizeof(name), "ns%u", ns->nsid); + ns->debugfs_dir = debugfs_create_dir(name, parent); + if (IS_ERR(ns->debugfs_dir)) { + ns->debugfs_dir = NULL; + return; + } +} + +void nvmet_debugfs_ns_free(struct nvmet_ns *ns) +{ + debugfs_remove_recursive(ns->debugfs_dir); + ns->debugfs_dir = NULL; +} + int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl) { char name[32]; diff --git a/drivers/nvme/target/debugfs.h b/drivers/nvme/target/debugfs.h index cfb8bbf6a297..b559d254fc2a 100644 --- a/drivers/nvme/target/debugfs.h +++ b/drivers/nvme/target/debugfs.h @@ -14,6 +14,8 @@ 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); +void nvmet_debugfs_ns_setup(struct nvmet_ns *ns); +void nvmet_debugfs_ns_free(struct nvmet_ns *ns); int __init nvmet_init_debugfs(void); void nvmet_exit_debugfs(void); @@ -30,6 +32,9 @@ static inline int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl) } static inline void nvmet_debugfs_ctrl_free(struct nvmet_ctrl *ctrl) {} +static inline void nvmet_debugfs_ns_setup(struct nvmet_ns *ns) {} +static inline void nvmet_debugfs_ns_free(struct nvmet_ns *ns) {} + static inline int __init nvmet_init_debugfs(void) { return 0; diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index aaba745e3c21..c672c9bf3053 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -128,6 +128,9 @@ struct nvmet_ns { u8 csi; struct nvmet_pr pr; struct xarray pr_per_ctrl_refs; +#ifdef CONFIG_NVME_TARGET_DEBUGFS + struct dentry *debugfs_dir; +#endif }; static inline struct nvmet_ns *to_nvmet_ns(struct config_item *item) From 1511516478bb9d718ae5c9deceb19cfffd1113da Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 7 Jul 2026 20:11:14 +0800 Subject: [PATCH 17/81] nvmet: expose reservation state through debugfs Add a 'reservation' debugfs file under each namespace directory that shows the persistent reservation state, including enable status, generation counter, notify mask, current holder info, and the full registrant list with hostid and reservation key. Each attribute is emitted as a single "key=value" line so the output is easy to parse from scripts. The registrant list is emitted as repeated "reg=" lines. The notify mask is emitted as a comma-separated list of masked notification names. Empty values are reported as "none". Example output: enable=1 generation=2 notify_mask=reg_preempted,resv_released,resv_preempted rtype=write_exclusive holder=11111111-1111-1111-1111-111111111111,0x1111 reg=11111111-1111-1111-1111-111111111111,0x1111 reg=22222222-2222-2222-2222-222222222222,0x2222 When reservation is not enabled only "enable=0" is printed. The output uses rcu_read_lock() for safe access to the holder and registrant_list, consistent with other PR read paths. Reviewed-by: Daniel Wagner Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/debugfs.c | 82 +++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/drivers/nvme/target/debugfs.c b/drivers/nvme/target/debugfs.c index e6f51eb59010..e85fe1d4c9f8 100644 --- a/drivers/nvme/target/debugfs.c +++ b/drivers/nvme/target/debugfs.c @@ -153,6 +153,86 @@ static int nvmet_ctrl_tls_concat_show(struct seq_file *m, void *p) NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_concat); #endif +static const char *const nvmet_pr_type_names[] = { + [NVME_PR_WRITE_EXCLUSIVE] = "write_exclusive", + [NVME_PR_EXCLUSIVE_ACCESS] = "exclusive_access", + [NVME_PR_WRITE_EXCLUSIVE_REG_ONLY] = "write_exclusive_reg_only", + [NVME_PR_EXCLUSIVE_ACCESS_REG_ONLY] = "exclusive_access_reg_only", + [NVME_PR_WRITE_EXCLUSIVE_ALL_REGS] = "write_exclusive_all_regs", + [NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS] = "exclusive_access_all_regs", +}; + +static const char *nvmet_pr_type_to_str(enum nvme_pr_type type) +{ + if (type < ARRAY_SIZE(nvmet_pr_type_names) && + nvmet_pr_type_names[type]) + return nvmet_pr_type_names[type]; + return "unknown"; +} + +static const char *const nvmet_pr_notify_names[] = { + [NVME_PR_NOTIFY_BIT_REG_PREEMPTED] = "reg_preempted", + [NVME_PR_NOTIFY_BIT_RESV_RELEASED] = "resv_released", + [NVME_PR_NOTIFY_BIT_RESV_PREEMPTED] = "resv_preempted", +}; + +static void nvmet_pr_notify_mask_to_str(struct seq_file *m, unsigned long mask) +{ + bool sep = false; + int i; + + if (!mask) { + seq_puts(m, "none"); + return; + } + + for (i = 0; i < ARRAY_SIZE(nvmet_pr_notify_names); i++) { + if (!test_bit(i, &mask) || !nvmet_pr_notify_names[i]) + continue; + if (sep) + seq_putc(m, ','); + seq_puts(m, nvmet_pr_notify_names[i]); + sep = true; + } +} + +static int nvmet_ns_pr_show(struct seq_file *m, void *p) +{ + struct nvmet_ns *ns = m->private; + struct nvmet_pr *pr = &ns->pr; + struct nvmet_pr_registrant *holder, *reg; + + seq_printf(m, "enable=%d\n", pr->enable); + if (!pr->enable) + return 0; + + seq_printf(m, "generation=%u\n", atomic_read(&pr->generation)); + seq_puts(m, "notify_mask="); + nvmet_pr_notify_mask_to_str(m, pr->notify_mask); + seq_putc(m, '\n'); + + rcu_read_lock(); + holder = rcu_dereference(pr->holder); + if (holder) { + seq_printf(m, "rtype=%s\n", + nvmet_pr_type_to_str(holder->rtype)); + seq_printf(m, "holder=%pUb,0x%llx\n", + &holder->hostid, holder->rkey); + } else { + seq_puts(m, "rtype=none\n"); + seq_puts(m, "holder=none\n"); + } + + list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { + seq_printf(m, "reg=%pUb,0x%llx\n", + ®->hostid, reg->rkey); + } + rcu_read_unlock(); + + return 0; +} +NVMET_DEBUGFS_ATTR(nvmet_ns_pr); + void nvmet_debugfs_ns_setup(struct nvmet_ns *ns) { char name[16]; @@ -166,6 +246,8 @@ void nvmet_debugfs_ns_setup(struct nvmet_ns *ns) ns->debugfs_dir = NULL; return; } + debugfs_create_file("reservation", 0400, ns->debugfs_dir, ns, + &nvmet_ns_pr_fops); } void nvmet_debugfs_ns_free(struct nvmet_ns *ns) From 1c4635cf4de92564ead2b4be402c405dcc375f01 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 7 Jul 2026 20:12:25 +0800 Subject: [PATCH 18/81] nvme: add ABI documentation for host sysfs interfaces Add Documentation/ABI/stable/sysfs-nvme documenting all NVMe host sysfs attributes, covering controller attributes under /sys/class/nvme/nvmeX/, namespace attributes under /sys/block/nvmeXnY/, and subsystem attributes under /sys/class/nvme-subsystem/nvme-subsysX/. Each entry has been traced to its original introducing commit to provide accurate Date, KernelVersion, and Contact information. Reviewed-by: Hannes Reinecke Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- Documentation/ABI/stable/sysfs-nvme | 453 +++++++++++++++++++++++++++ Documentation/ABI/testing/sysfs-nvme | 13 - 2 files changed, 453 insertions(+), 13 deletions(-) create mode 100644 Documentation/ABI/stable/sysfs-nvme delete mode 100644 Documentation/ABI/testing/sysfs-nvme diff --git a/Documentation/ABI/stable/sysfs-nvme b/Documentation/ABI/stable/sysfs-nvme new file mode 100644 index 000000000000..a2f5d0710db4 --- /dev/null +++ b/Documentation/ABI/stable/sysfs-nvme @@ -0,0 +1,453 @@ +What: /sys/class/nvme/nvmeX/model +What: /sys/class/nvme/nvmeX/serial +What: /sys/class/nvme/nvmeX/firmware_rev +Date: January 2016 +KernelVersion: 4.5 +Contact: Keith Busch +Description: + Shows the model, serial number, or firmware revision string + of the NVMe controller, as reported in the Identify + Controller data structure. + +What: /sys/class/nvme/nvmeX/cntlid +Date: February 2016 +KernelVersion: 4.6 +Contact: Ming Lin +Description: + Shows the controller identifier assigned by the NVMe + subsystem. + +What: /sys/class/nvme/nvmeX/cntrltype +What: /sys/class/nvme/nvmeX/dctype +Date: February 2022 +KernelVersion: 5.18 +Contact: Martin Belanger +Description: + cntrltype: Shows the controller type. Possible values: "io", + "discovery", "admin", "reserved". + + dctype: Shows the discovery controller type. Possible values: + "none", "ddc", "cdc", "reserved". + +What: /sys/class/nvme/nvmeX/reset_controller +Date: November 2015 +KernelVersion: 4.5 +Contact: Christoph Hellwig +Description: + Write-only. Writing any value triggers a synchronous + controller reset. + +What: /sys/class/nvme/nvmeX/rescan_controller +Date: April 2016 +KernelVersion: 4.7 +Contact: Keith Busch +Description: + Write-only. Writing any value triggers a namespace rescan + on this controller. + +What: /sys/class/nvme/nvmeX/transport +What: /sys/class/nvme/nvmeX/subsysnqn +What: /sys/class/nvme/nvmeX/address +What: /sys/class/nvme/nvmeX/delete_controller +What: /sys/class/nvme/nvmeX/reconnect_delay +What: /sys/class/nvme/nvmeX/ctrl_loss_tmo +Date: June 2016 +KernelVersion: 4.8 +Contact: Ming Lin +Description: + Fabrics controller attributes added with NVMe-oF support. + + transport: Shows the transport type string. Possible values: + "pcie", "tcp", "rdma", "fc", "loop". + + subsysnqn: Shows the NVMe Qualified Name (NQN) of the + subsystem this controller belongs to. + + address: Shows the transport-specific address string. Only + available for fabrics controllers. + + delete_controller: Write-only. Triggers deletion of this + fabrics controller. + + reconnect_delay: Shows or sets the reconnect delay in + seconds. Reading returns the delay value, or "off" if + disabled. + + ctrl_loss_tmo: Shows or sets the controller loss timeout in + seconds. Reading returns the timeout value, or "off" if + infinite reconnects are allowed. Writing a negative value + disables the timeout. + +What: /sys/class/nvme/nvmeX/hostnqn +What: /sys/class/nvme/nvmeX/hostid +Date: February 2020 +KernelVersion: 5.7 +Contact: Sagi Grimberg +Description: + hostnqn: Shows the host NQN used by this fabrics controller. + + hostid: Shows the host identifier (UUID format) used by this + fabrics controller. + + Only available for fabrics controllers. + +What: /sys/class/nvme/nvmeX/fast_io_fail_tmo +Date: November 2020 +KernelVersion: 5.11 +Contact: Victor Gladkov +Description: + Shows or sets the fast I/O fail timeout in seconds. Reading + returns the timeout value, or "off" if disabled. Writing a + negative value disables the fast I/O fail. Only available + for fabrics controllers. + +What: /sys/class/nvme/nvmeX/kato +Date: April 2021 +KernelVersion: 5.13 +Contact: Hannes Reinecke +Description: + Shows the Keep Alive Timeout value in milliseconds for + this controller. + +What: /sys/class/nvme/nvmeX/cmb +Date: October 2016 +KernelVersion: 4.9 +Contact: Stephen Bates +Description: + Shows the Controller Memory Buffer (CMB) register values + in format "cmbloc : 0x%08x\ncmbsz : 0x%08x\n". Only + visible when the controller has a CMB (cmbsz != 0). + PCI transport only. + +What: /sys/class/nvme/nvmeX/cmbloc +What: /sys/class/nvme/nvmeX/cmbsz +What: /sys/class/nvme/nvmeX/hmb +Date: July 2021 +KernelVersion: 5.15 +Contact: Keith Busch +Description: + cmbloc: Shows the CMBLOC register value. + + cmbsz: Shows the CMBSZ register value. + + cmbloc and cmbsz are only visible when the controller has + a CMB. PCI transport only. + + hmb: Shows or sets whether the Host Memory Buffer (HMB) is + enabled. Reading returns 1 (enabled) or 0 (disabled). + Writing 1 enables HMB; writing 0 disables it. Only + visible when the controller supports HMB (hmpre != 0). + PCI transport only. + +What: /sys/class/nvme/nvmeX/state +Date: November 2016 +KernelVersion: 4.11 +Contact: Sagi Grimberg +Description: + Shows the current state of the controller. Possible values: + "new", "live", "resetting", "connecting", "deleting", + "deleting (no IO)", "dead". + +What: /sys/class/nvme/nvmeX/numa_node +Date: November 2018 +KernelVersion: 5.0 +Contact: Hannes Reinecke +Description: + Shows the NUMA node the controller is attached to. + +What: /sys/class/nvme/nvmeX/queue_count +What: /sys/class/nvme/nvmeX/sqsize +Date: September 2019 +KernelVersion: 5.4 +Contact: James Smart +Description: + queue_count: Shows the total number of queues (admin + I/O) + for this controller. + + sqsize: Shows the submission queue size for this controller. + +What: /sys/class/nvme/nvmeX/dhchap_secret +What: /sys/class/nvme/nvmeX/dhchap_ctrl_secret +Date: June 2022 +KernelVersion: 6.0 +Contact: Hannes Reinecke +Description: + dhchap_secret: Shows or sets the host DH-HMAC-CHAP secret + for this controller. Reading returns "none" if not set. + Writing must use the "DHHC-1:" key format and triggers + re-authentication. + + dhchap_ctrl_secret: Shows or sets the controller + DH-HMAC-CHAP secret for bidirectional authentication. + Same format as dhchap_secret. + + Only available when CONFIG_NVME_HOST_AUTH is enabled and + for fabrics controllers. + +What: /sys/class/nvme/nvmeX/tls_key +Date: August 2023 +KernelVersion: 6.7 +Contact: Hannes Reinecke +Description: + Shows the serial of the currently active TLS PSK as hex. + Returns empty if no TLS key is active. Only available for + TCP controllers with TLS or secure concatenation enabled + (CONFIG_NVME_TCP_TLS). + +What: /sys/class/nvme/nvmeX/tls_configured_key +Date: July 2024 +KernelVersion: 6.12 +Contact: Hannes Reinecke +Description: + Shows the serial of the configured TLS key. Writing 0 + triggers a PSK reauthentication (REPLACETLSPSK) with + the target. After reauthentication the returned serial + will be the new key. Only available for TCP controllers + with secure concatenation enabled (CONFIG_NVME_TCP_TLS). + +What: /sys/class/nvme/nvmeX/tls_keyring +Date: July 2024 +KernelVersion: 6.12 +Contact: Hannes Reinecke +Description: + Shows the TLS keyring description. Only available for TCP + controllers with a keyring configured (CONFIG_NVME_TCP_TLS). + +What: /sys/class/nvme/nvmeX/tls_mode +Date: April 2026 +KernelVersion: 7.1 +Contact: Daniel Wagner +Description: + Shows the TLS mode: "tls" for direct TLS or "concat" for + secure concatenation. Only available for TCP controllers + with TLS or secure concatenation enabled + (CONFIG_NVME_TCP_TLS). + +What: /sys/class/nvme/nvmeX/passthru_err_log_enabled +Date: January 2024 +KernelVersion: 6.8 +Contact: Alan Adamson +Description: + Shows or sets whether admin passthrough error logging is + enabled for this controller. Reading returns "on" or "off". + Writing accepts a boolean value. + +What: /sys/class/nvme/nvmeX/quirks +Date: November 2025 +KernelVersion: 7.0 +Contact: Maurizio Lombardi +Description: + Shows the active quirk names for this controller, one per + line. Shows "none" if no quirks are active. + +What: /sys/class/nvme/nvmeX/admin_timeout +What: /sys/class/nvme/nvmeX/io_timeout +Date: May 2026 +KernelVersion: 7.2 +Contact: Maurizio Lombardi +Description: + admin_timeout: Shows or sets the admin command timeout in + milliseconds. + + io_timeout: Shows or sets the I/O command timeout in + milliseconds. Changes are propagated to all namespace + request queues. + + The value must be nonzero. Only writable after the + controller has been started at least once. + +What: /sys/block/nvmeXnY/uuid +What: /sys/block/nvmeXnY/eui +What: /sys/block/nvmeXnY/nsid +Date: December 2015 +KernelVersion: 4.5 +Contact: Keith Busch +Description: + Namespace identification attributes. + + uuid: Shows the UUID for this namespace. Falls back to + showing the NGUID for backward compatibility. Hidden if + both are all zeros. + + eui: Shows the IEEE Extended Unique Identifier (EUI-64). + Hidden if all zeros. + + nsid: Shows the namespace identifier (NSID). + +What: /sys/block/nvmeXnY/wwid +Date: February 2016 +KernelVersion: 4.6 +Contact: Keith Busch +Description: + Shows the World Wide Identifier for this namespace. The + format depends on available identifiers (in priority + order): "uuid.{UUID}", "eui.{NGUID}", "eui.{EUI64}", or + "nvme.{VID}-{SERIAL}-{MODEL}-{NSID}". + +What: /sys/block/nvmeXnY/nguid +Date: June 2017 +KernelVersion: 4.13 +Contact: Johannes Thumshirn +Description: + Shows the Namespace Globally Unique Identifier (NGUID). + Hidden if the NGUID is all zeros. + +What: /sys/block/nvmeXcYnZ/ana_grpid +What: /sys/block/nvmeXcYnZ/ana_state +Date: May 2018 +KernelVersion: 4.19 +Contact: Christoph Hellwig +Description: + ana_grpid: Shows the ANA Group ID for this namespace + path device. + + ana_state: Shows the ANA state. Possible values: + "optimized", "non-optimized", "inaccessible", + "persistent-loss", "change". + + Only visible when the controller supports ANA. + Requires CONFIG_NVME_MULTIPATH. + +What: /sys/block/nvmeXcYnZ/queue_depth +Date: June 2024 +KernelVersion: 6.11 +Contact: Thomas Song +Description: + Shows the current active I/O count on this path's + controller. Returns empty if iopolicy is not "queue-depth". + Requires CONFIG_NVME_MULTIPATH. + +What: /sys/block/nvmeXcYnZ/numa_nodes +Date: January 2025 +KernelVersion: 6.15 +Contact: Nilay Shroff +Description: + Shows the NUMA node mask for which this path is the + currently selected path. Returns empty if iopolicy is not + "numa". Requires CONFIG_NVME_MULTIPATH. + +What: /sys/block/nvmeXnY/delayed_removal_secs +Date: May 2025 +KernelVersion: 6.16 +Contact: Nilay Shroff +Description: + Shows or sets the delayed removal timeout in seconds for + the multipath head device. When nonzero, I/O is queued + instead of failed when all paths are gone, and head removal + is deferred. Only visible on multipath head devices. + Requires CONFIG_NVME_MULTIPATH. + +What: /sys/block/nvmeXnY/csi +What: /sys/block/nvmeXnY/metadata_bytes +What: /sys/block/nvmeXnY/nuse +Date: December 2023 +KernelVersion: 6.8 +Contact: Daniel Wagner +Description: + csi: Shows the Command Set Identifier for this namespace. + + metadata_bytes: Shows the metadata size in bytes. + + nuse: Shows the Namespace Utilization (NUSE) value. Reading + triggers an Identify Namespace command to refresh the + value (rate-limited to avoid excessive commands). + +What: /sys/block/nvmeXnY/passthru_err_log_enabled +Date: January 2024 +KernelVersion: 6.8 +Contact: Alan Adamson +Description: + Shows or sets whether I/O passthrough error logging is + enabled for this namespace. Reading returns "on" or "off". + Writing accepts a boolean value. + +What: /sys/class/nvme/nvmeX/diag/command_error_count +What: /sys/class/nvme/nvmeX/diag/reset_count +What: /sys/class/nvme/nvmeX/diag/reconnect_count +Date: May 2026 +KernelVersion: 7.2 +Contact: Nilay Shroff +Description: + Controller diagnostic counters. + + command_error_count: Admin command error counter. + + reset_count: Controller reset counter. + + reconnect_count: Accumulated reconnect counter. Only + available for fabrics controllers. + + All counters can be reset by writing a value. + +What: /sys/block/nvmeXnY/diag/command_retries_count +What: /sys/block/nvmeXnY/diag/command_error_count +Date: May 2026 +KernelVersion: 7.2 +Contact: Nilay Shroff +Description: + Namespace diagnostic counters for non-multipath + configurations (when CONFIG_NVME_MULTIPATH is not + configured). + + command_retries_count: I/O command retry counter. + + command_error_count: I/O command error counter. + + All counters can be reset by writing any value. + +What: /sys/block/nvmeXcYnZ/diag/command_retries_count +What: /sys/block/nvmeXcYnZ/diag/command_error_count +What: /sys/block/nvmeXcYnZ/diag/multipath_failover_count +What: /sys/block/nvmeXnY/diag/io_requeue_no_usable_path_count +What: /sys/block/nvmeXnY/diag/io_fail_no_available_path_count +Date: May 2026 +KernelVersion: 7.2 +Contact: Nilay Shroff +Description: + Namespace diagnostic counters for multipath + configurations (when CONFIG_NVME_MULTIPATH is + configured). + + command_retries_count: I/O command retry counter. + + command_error_count: I/O command error counter. + + multipath_failover_count: Multipath failover counter. + + io_requeue_no_usable_path_count: Counter of I/Os + requeued because no usable path was available. + + io_fail_no_available_path_count: Counter of I/Os + failed because no available path existed. + + All counters can be reset by writing any value. + +What: /sys/class/nvme-subsystem/nvme-subsysX/model +What: /sys/class/nvme-subsystem/nvme-subsysX/serial +What: /sys/class/nvme-subsystem/nvme-subsysX/firmware_rev +What: /sys/class/nvme-subsystem/nvme-subsysX/subsysnqn +Date: November 2017 +KernelVersion: 4.15 +Contact: Hannes Reinecke +Description: + Shows the model, serial number, firmware revision, or NQN + of the NVMe subsystem. + +What: /sys/class/nvme-subsystem/nvme-subsysX/iopolicy +Date: February 2019 +KernelVersion: 5.1 +Contact: Hannes Reinecke +Description: + Shows or sets the multipath I/O path selection policy for + this subsystem. Accepted values: "numa", "round-robin", + "queue-depth". Changing the policy clears all current path + selections. Only available when CONFIG_NVME_MULTIPATH is + enabled. + +What: /sys/class/nvme-subsystem/nvme-subsysX/subsystype +Date: September 2021 +KernelVersion: 5.16 +Contact: Hannes Reinecke +Description: + Shows the subsystem type. Possible values: "discovery", + "nvm", "reserved". diff --git a/Documentation/ABI/testing/sysfs-nvme b/Documentation/ABI/testing/sysfs-nvme deleted file mode 100644 index 499d5f843cd4..000000000000 --- a/Documentation/ABI/testing/sysfs-nvme +++ /dev/null @@ -1,13 +0,0 @@ -What: /sys/devices/virtual/nvme-fabrics/ctl/.../tls_configured_key -Date: November 2025 -KernelVersion: 6.19 -Contact: Linux NVMe mailing list -Description: - The file is avaliable when using a secure concatanation - connection to a NVMe target. Reading the file will return - the serial of the currently negotiated key. - - Writing 0 to the file will trigger a PSK reauthentication - (REPLACETLSPSK) with the target. After a reauthentication - the value returned by tls_configured_key will be the new - serial. From 5d92321c83b63c6c548bbd8a7fca4405ff30add3 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 7 Jul 2026 20:12:26 +0800 Subject: [PATCH 19/81] nvmet: add ABI documentation for target configfs interfaces Add Documentation/ABI/stable/configfs-nvmet documenting all NVMe target configfs attributes, covering port attributes, subsystem attributes, namespace attributes, host authentication, passthrough mode, and ANA configuration. Each entry has been traced to its original introducing commit to provide accurate Date, KernelVersion, and Contact information. Reviewed-by: Hannes Reinecke Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- Documentation/ABI/stable/configfs-nvmet | 352 ++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 Documentation/ABI/stable/configfs-nvmet diff --git a/Documentation/ABI/stable/configfs-nvmet b/Documentation/ABI/stable/configfs-nvmet new file mode 100644 index 000000000000..36b587404ee7 --- /dev/null +++ b/Documentation/ABI/stable/configfs-nvmet @@ -0,0 +1,352 @@ +What: /config/nvmet/ports/N/addr_adrfam +What: /config/nvmet/ports/N/addr_portid +What: /config/nvmet/ports/N/addr_traddr +What: /config/nvmet/ports/N/addr_trsvcid +What: /config/nvmet/ports/N/addr_trtype +What: /config/nvmet/ports/N/addr_treq +Date: June 2016 +KernelVersion: 4.8 +Contact: Christoph Hellwig +Description: + Address attributes for an NVMe-oF target port. + + addr_adrfam: Shows or sets the address family. Accepted + values: "pcie", "ipv4", "ipv6", "ib", "fc", "pci", "loop". + + addr_portid: Shows or sets the port identifier (u16). + + addr_traddr: Shows or sets the transport address string. + + addr_trsvcid: Shows or sets the transport service identifier. + + addr_trtype: Shows or sets the transport type. Accepted + values: "rdma", "fc", "tcp", "pci", "loop". Also + initializes default TSAS values. + + addr_treq: Shows or sets the transport security requirements. + Accepted values: "not specified", "required", + "not required". For TCP with TLS1.3, "not specified" is + rejected. + + All attributes require the port to be disabled before + modification. + +What: /config/nvmet/ports/N/referrals/NAME/addr_adrfam +What: /config/nvmet/ports/N/referrals/NAME/addr_portid +What: /config/nvmet/ports/N/referrals/NAME/addr_traddr +What: /config/nvmet/ports/N/referrals/NAME/addr_trsvcid +What: /config/nvmet/ports/N/referrals/NAME/addr_trtype +What: /config/nvmet/ports/N/referrals/NAME/addr_treq +What: /config/nvmet/ports/N/referrals/NAME/enable +Date: June 2016 +KernelVersion: 4.8 +Contact: Christoph Hellwig +Description: + Address attributes and enable control for a referral entry + under a port. The addr_* attributes have the same semantics + as the corresponding port-level attributes. The enable + attribute shows or sets whether this referral is enabled + (boolean). + +What: /config/nvmet/ports/N/param_inline_data_size +Date: June 2018 +KernelVersion: 4.19 +Contact: Steve Wise +Description: + Shows or sets the inline data size for this port. Default + is -1 which lets the transport choose. The port must be + disabled before modification. + +What: /config/nvmet/ports/N/ana_groups/ID/ana_state +Date: June 2018 +KernelVersion: 4.19 +Contact: Christoph Hellwig +Description: + Shows or sets the ANA (Asymmetric Namespace Access) state + for this group on this port. Accepted values: "optimized", + "non-optimized", "inaccessible", "persistent-loss", + "change". Changes trigger an ANA change event. + +What: /config/nvmet/ports/N/param_pi_enable +Date: May 2020 +KernelVersion: 5.8 +Contact: Israel Rukshin +Description: + Shows or sets whether protection information (PI) is + enabled/supported for this port. Accepts boolean value. + Only available when CONFIG_BLK_DEV_INTEGRITY is enabled. + The port must be disabled before modification. + +What: /config/nvmet/ports/N/addr_tsas +Date: August 2023 +KernelVersion: 6.7 +Contact: Hannes Reinecke +Description: + Shows or sets the transport-specific address subtype. For + TCP transport, accepted values: "none", "tls1.3" (requires + CONFIG_NVME_TARGET_TCP_TLS). For RDMA transport, shows the + QP type: "connected" or "datagram". The port must be + disabled before modification. + +What: /config/nvmet/ports/N/param_max_queue_size +Date: January 2024 +KernelVersion: 6.9 +Contact: Max Gurtovoy +Description: + Shows or sets the maximum queue size for this port. Default + is -1 which lets the transport choose. The port must be + disabled before modification. + +What: /config/nvmet/ports/N/param_mdts +Date: April 2026 +KernelVersion: 7.1 +Contact: Aurelien Aptel +Description: + Shows or sets the maximum data transfer size for this port. + Default is -1 which lets the transport choose. The port + must be disabled before modification. + +What: /config/nvmet/subsystems/NAME/attr_allow_any_host +Date: June 2016 +KernelVersion: 4.8 +Contact: Christoph Hellwig +Description: + Shows or sets whether any host is allowed to connect. + Accepts boolean value. Cannot be set to 1 if explicit + hosts are linked in the allowed_hosts/ directory. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/device_path +What: /config/nvmet/subsystems/NAME/namespaces/NSID/device_nguid +What: /config/nvmet/subsystems/NAME/namespaces/NSID/enable +Date: June 2016 +KernelVersion: 4.8 +Contact: Christoph Hellwig +Description: + Namespace attributes added with the initial NVMe target. + + device_path: Shows or sets the backend block device path. + The namespace must be disabled before modification. + + device_nguid: Shows or sets the NGUID (128-bit identifier). + Accepts 32 hex digits with optional "-" or ":" separators. + The namespace must be disabled before modification. + + enable: Shows or sets whether this namespace is enabled + (boolean). + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/device_uuid +Date: June 2017 +KernelVersion: 4.13 +Contact: Johannes Thumshirn +Description: + Shows or sets the UUID for this namespace. The namespace + must be disabled before modification. + +What: /config/nvmet/subsystems/NAME/attr_version +What: /config/nvmet/subsystems/NAME/attr_serial +Date: July 2017 +KernelVersion: 4.13 +Contact: Johannes Thumshirn +Description: + attr_version: Shows or sets the NVMe version reported by + this subsystem. Format: "major.minor" or + "major.minor.tertiary". Cannot be changed after the + subsystem has been discovered. + + attr_serial: Shows or sets the serial number. Must be a + 1-20 byte ASCII string (characters 0x20-0x7e). Cannot be + changed after the subsystem has been discovered. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/ana_grpid +Date: June 2018 +KernelVersion: 4.19 +Contact: Christoph Hellwig +Description: + Shows or sets the ANA (Asymmetric Namespace Access) Group + ID for this namespace. Must be between 1 and 128. Changing + triggers an ANA event notification. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/buffered_io +Date: June 2018 +KernelVersion: 4.19 +Contact: Chaitanya Kulkarni +Description: + Shows or sets whether buffered I/O is used for this + namespace. Accepts boolean value. The namespace must be + disabled before modification. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/p2pmem +Date: October 2018 +KernelVersion: 4.20 +Contact: Logan Gunthorpe +Description: + Shows or sets the P2P DMA memory device for this namespace. + Accepts a PCI device BDF, "auto", or "none". The namespace + must be disabled before modification. Only available when + CONFIG_PCI_P2PDMA is enabled. + +What: /config/nvmet/subsystems/NAME/attr_cntlid_min +What: /config/nvmet/subsystems/NAME/attr_cntlid_max +Date: January 2020 +KernelVersion: 5.7 +Contact: Chaitanya Kulkarni +Description: + attr_cntlid_min: Shows or sets the minimum controller ID + (u16). Must be nonzero and not greater than attr_cntlid_max. + + attr_cntlid_max: Shows or sets the maximum controller ID + (u16). Must be nonzero and not less than attr_cntlid_min. + +What: /config/nvmet/subsystems/NAME/attr_model +Date: January 2020 +KernelVersion: 5.7 +Contact: Mark Ruijter +Description: + Shows or sets the model number for this subsystem. Must + be a 1-40 byte ASCII string (characters 0x20-0x7e). + Cannot be changed after the subsystem has been discovered. + +What: /config/nvmet/subsystems/NAME/attr_pi_enable +Date: May 2020 +KernelVersion: 5.8 +Contact: Israel Rukshin +Description: + Shows or sets whether protection information (PI) is + enabled/supported for this subsystem. Accepts boolean + value. Only available when CONFIG_BLK_DEV_INTEGRITY is + enabled. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/revalidate_size +Date: May 2020 +KernelVersion: 5.8 +Contact: Chaitanya Kulkarni +Description: + Write-only. Writing 1 triggers namespace size revalidation. + If the size has changed, a namespace changed AEN is sent. + The namespace must be enabled. + +What: /config/nvmet/subsystems/NAME/passthru/device_path +What: /config/nvmet/subsystems/NAME/passthru/enable +Date: July 2020 +KernelVersion: 5.9 +Contact: Logan Gunthorpe +Description: + Passthrough mode attributes. + + device_path: Shows or sets the NVMe controller character + device path (e.g., /dev/nvme0). Cannot be changed while + the passthrough controller is active. + + enable: Shows or sets whether passthrough mode is enabled + (boolean). + + Only available when CONFIG_NVME_TARGET_PASSTHRU is enabled. + +What: /config/nvmet/subsystems/NAME/passthru/admin_timeout +What: /config/nvmet/subsystems/NAME/passthru/io_timeout +Date: November 2020 +KernelVersion: 5.11 +Contact: Chaitanya Kulkarni +Description: + admin_timeout: Shows or sets the admin command timeout for + passthrough mode, in jiffies. + + io_timeout: Shows or sets the I/O command timeout for + passthrough mode, in jiffies. + + Only available when CONFIG_NVME_TARGET_PASSTHRU is enabled. + +What: /config/nvmet/subsystems/NAME/passthru/clear_ids +Date: June 2022 +KernelVersion: 5.19 +Contact: Alan Adamson +Description: + Shows or sets whether to clear identify data IDs in + passthrough mode. Only available when + CONFIG_NVME_TARGET_PASSTHRU is enabled. + +What: /config/nvmet/subsystems/NAME/attr_qid_max +Date: August 2022 +KernelVersion: 6.1 +Contact: Daniel Wagner +Description: + Shows or sets the maximum queue ID (number of I/O queues, + u16). Must be between 1 and 128. Changing this value + forces reconnection of all connected controllers. + +What: /config/nvmet/subsystems/NAME/attr_ieee_oui +Date: November 2022 +KernelVersion: 6.2 +Contact: Aleksandr Miloserdov +Description: + Shows or sets the IEEE OUI for this subsystem. Displayed + in "0x%06x" format. Must be a 24-bit value. Cannot be + changed after the subsystem has been discovered. + +What: /config/nvmet/subsystems/NAME/attr_firmware +Date: November 2022 +KernelVersion: 6.2 +Contact: Aleksandr Miloserdov +Description: + Shows or sets the firmware revision string for this + subsystem. Must be a 1-8 byte ASCII string (characters + 0x20-0x7e). Cannot be changed after the subsystem has + been discovered. + +What: /config/nvmet/subsystems/NAME/namespaces/NSID/resv_enable +Date: November 2024 +KernelVersion: 6.13 +Contact: Guixin Liu +Description: + Shows or sets whether persistent reservation support is + enabled for this namespace. Accepts boolean value. The + namespace must be disabled before modification. + +What: /config/nvmet/subsystems/NAME/attr_vendor_id +What: /config/nvmet/subsystems/NAME/attr_subsys_vendor_id +Date: January 2025 +KernelVersion: 6.14 +Contact: Damien Le Moal +Description: + attr_vendor_id: Shows or sets the PCI vendor ID reported + by this subsystem. Displayed in "0x%x" format. + + attr_subsys_vendor_id: Shows or sets the PCI subsystem + vendor ID. Displayed in "0x%x" format. + +What: /config/nvmet/hosts/HOSTNQN/dhchap_key +What: /config/nvmet/hosts/HOSTNQN/dhchap_ctrl_key +What: /config/nvmet/hosts/HOSTNQN/dhchap_hash +What: /config/nvmet/hosts/HOSTNQN/dhchap_dhgroup +Date: June 2022 +KernelVersion: 6.0 +Contact: Hannes Reinecke +Description: + DH-HMAC-CHAP authentication attributes. + + dhchap_key: Shows or sets the host secret key. Accepts a + key string in "DHHC-1:" format. + + dhchap_ctrl_key: Shows or sets the controller secret key + for bidirectional authentication. Same format as dhchap_key. + + dhchap_hash: Shows or sets the HMAC hash algorithm. + Accepted values: "hmac(sha256)", "hmac(sha384)", + "hmac(sha512)". + + dhchap_dhgroup: Shows or sets the Diffie-Hellman group for + DH-HMAC-CHAP key exchange. Accepted values: "null", + "ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144". + Non-null groups require the corresponding KPP crypto + algorithm to be available. + + Only available when CONFIG_NVME_TARGET_AUTH is enabled. + +What: /config/nvmet/discovery_nqn +Date: April 2024 +KernelVersion: 6.9 +Contact: Hannes Reinecke +Description: + Shows or sets the NQN of the discovery subsystem. The + value must be unique and not duplicate any existing + subsystem name. From cdf9a65e80ec874b630502946d269fac38dc5de8 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 7 Jul 2026 20:12:27 +0800 Subject: [PATCH 20/81] MAINTAINERS: add missing NVMe documentation files Add documentation file entries that were missing from the NVM EXPRESS DRIVER and NVM EXPRESS TARGET DRIVER sections, so patches touching these files are properly routed to the NVMe mailing list and maintainers. Reviewed-by: Hannes Reinecke Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- MAINTAINERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3d6db8cb608f..5bbc5b49d36a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19207,6 +19207,9 @@ L: linux-nvme@lists.infradead.org S: Supported W: http://git.infradead.org/nvme.git T: git git://git.infradead.org/nvme.git +F: Documentation/ABI/stable/sysfs-nvme +F: Documentation/admin-guide/nvme-multipath.rst +F: Documentation/fault-injection/nvme-fault-injection.rst F: Documentation/nvme/ F: drivers/nvme/common/ F: drivers/nvme/host/ @@ -19249,6 +19252,7 @@ L: linux-nvme@lists.infradead.org S: Supported W: http://git.infradead.org/nvme.git T: git git://git.infradead.org/nvme.git +F: Documentation/ABI/stable/configfs-nvmet F: drivers/nvme/target/ NVMEM FRAMEWORK From 3c568b35a0d309acb40746552bec2af24cd550ef Mon Sep 17 00:00:00 2001 From: Hari Mishal Date: Thu, 9 Jul 2026 14:30:32 +0200 Subject: [PATCH 21/81] nvme: bound ns descriptor header and body to identify buffer nvme_identify_ns_descs() allocates a buffer and gives it to the controller, which populates it and then iterates the buffer with variable byte increments that vary by type and body size. But, there is no bounds check inside the iteration itself except the loop bound itself. Fix this by checking and stopping iteration if the next header or its declared body would go past the buffer itself. Assisted-by: gkh_clanker_t1000 Reviewed-by: Christoph Hellwig Signed-off-by: Hari Mishal Signed-off-by: Greg Kroah-Hartman Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index db0c8ad4628a..0b8330c79b1a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -1583,8 +1583,12 @@ static int nvme_identify_ns_descs(struct nvme_ctrl *ctrl, for (pos = 0; pos < NVME_IDENTIFY_DATA_SIZE; pos += len) { struct nvme_ns_id_desc *cur = data + pos; + if (pos + sizeof(*cur) > NVME_IDENTIFY_DATA_SIZE) + break; if (cur->nidl == 0) break; + if (pos + sizeof(*cur) + cur->nidl > NVME_IDENTIFY_DATA_SIZE) + break; len = nvme_process_ns_desc(ctrl, &info->ids, cur, &csi_seen); if (len < 0) From 29261f8bb41662f2a660c479e5cf592942b53f78 Mon Sep 17 00:00:00 2001 From: Hari Mishal Date: Thu, 9 Jul 2026 14:30:33 +0200 Subject: [PATCH 22/81] nvme: clamp FDP nruhsd to allocated RUH status descriptor count nvme_query_fdp_info() allocates the RUH status buffer for at most S8_MAX - 1 descriptors, and then copies ruhs->ruhsd[] into head->plids[] using the controller reported ruhs->nruhsd directly as the loop bound. However, that count wasn't taken into account for the actual buffer's size, so there was a chance for a controller reporting a larger nruhsd to cause the copy to overflow the buffer. Clamp nr_plids to the same bound used for the allocation. Assisted-by: gkh_clanker_t1000 Reviewed-by: Christoph Hellwig Signed-off-by: Hari Mishal Signed-off-by: Greg Kroah-Hartman Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 0b8330c79b1a..cdb16e949e2a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2361,7 +2361,7 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) goto free; } - head->nr_plids = le16_to_cpu(ruhs->nruhsd); + head->nr_plids = min(le16_to_cpu(ruhs->nruhsd), S8_MAX - 1); if (!head->nr_plids) goto free; From a11e0a4cb4189c468683a2f384f0c266c26a497f Mon Sep 17 00:00:00 2001 From: John Garry Date: Mon, 13 Jul 2026 10:42:37 +0000 Subject: [PATCH 23/81] nvme: add nvme_get_ns_head() Add a wrapper for getting a reference to the NS head. This would be used in scenarios when we know that getting a reference would not fail. Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 5 +++++ drivers/nvme/host/multipath.c | 2 +- drivers/nvme/host/nvme.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index cdb16e949e2a..882920b7327b 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -693,6 +693,11 @@ static void nvme_free_ns_head(struct kref *ref) kfree(head); } +void nvme_get_ns_head(struct nvme_ns_head *head) +{ + kref_get(&head->ref); +} + bool nvme_tryget_ns_head(struct nvme_ns_head *head) { return kref_get_unless_zero(&head->ref); diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 56587ae59c7f..8cb417036fe1 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -797,7 +797,7 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) set_bit(GD_SUPPRESS_PART_SCAN, &head->disk->state); sprintf(head->disk->disk_name, "nvme%dn%d", ctrl->subsys->instance, head->instance); - nvme_tryget_ns_head(head); + nvme_get_ns_head(head); return 0; } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index a679a4c61462..2e9dea6420da 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -995,6 +995,7 @@ int nvme_delete_ctrl(struct nvme_ctrl *ctrl); void nvme_queue_scan(struct nvme_ctrl *ctrl); int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, void *log, size_t size, u64 offset); +void nvme_get_ns_head(struct nvme_ns_head *head); bool nvme_tryget_ns_head(struct nvme_ns_head *head); void nvme_put_ns_head(struct nvme_ns_head *head); int nvme_cdev_add(const char *name, struct cdev *cdev, From 9952f3882ecba709092379b71e02b09762b26f96 Mon Sep 17 00:00:00 2001 From: John Garry Date: Mon, 13 Jul 2026 10:42:38 +0000 Subject: [PATCH 24/81] nvme: fix cdev lifetime Sashiko bot reported a potential problem for the cdev lifetime in [0] - the code there is heavily based on the NVMe code. Currently the NS head .open and .release file_operations methods take and put a reference to the nvme_ns_head to ensure that this structure does not disappear while we open fds for that cdev. In multipath mode, when we teardown the NS head, we call nvme_cdev_del() -> cdev_device_del() -> cdev_del(). However after cdev_del() returns, cdevs already open will remain and their fops will still be callable. As such, we can still reference the cdev after the nvme_ns_head reference count drops to 0 (and is freed). This can be shown with an application which delays between opening the cdev and issuing the ioctl while the NS head is being torn down: # ./ioctl_file /dev/ng1n1 & # waiting 10 seconds .... # ./ini_nvme_teardown.sh [ 21.221718] nvme nvme1: Removing ctrl: NQN "nvme-test-target" [ 21.274609] nvme nvme2: Removing ctrl: NQN "nvme-test-target" # now going to issue ioctl .... [ 26.549285] ================================================================== [ 26.550841] BUG: KASAN: slab-use-after-free in cdev_put.part.0+0x3d/0x40 [ 26.552352] Read of size 8 at addr ffff88811e7fa170 by task ioctl_file/237 [ 26.553805] [ 26.554227] CPU: 3 UID: 0 PID: 237 Comm: ioctl_file Not tainted 7.2.0-rc1-00004-g6852a10e32d4 #921 PREEMPT(lazy) [ 26.554236] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 26.554241] Call Trace: [ 26.554245] [ 26.554248] dump_stack_lvl+0x68/0xa0 [ 26.554266] print_report+0x10d/0x5d0 [ 26.554276] ? __virt_addr_valid+0x21d/0x3f0 [ 26.554287] ? cdev_put.part.0+0x3d/0x40 [ 26.554292] kasan_report+0x96/0xd0 [ 26.554300] ? cdev_put.part.0+0x3d/0x40 [ 26.554307] cdev_put.part.0+0x3d/0x40 [ 26.554313] __fput+0x7bc/0xa70 [ 26.554322] fput_close_sync+0xd8/0x190 [ 26.554328] ? __pfx_fput_close_sync+0x10/0x10 [ 26.554337] __x64_sys_close+0x79/0xd0 [ 26.554344] do_syscall_64+0x117/0x6b0 [ 26.554351] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 26.554358] RIP: 0033:0x7f938c067727 [ 26.554364] Code: 48 89 fa 4c 89 df e8 28 ad 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5bf [ 26.554369] RSP: 002b:00007fff49f05980 EFLAGS: 00000202 ORIG_RAX: 0000000000000003 [ 26.554376] RAX: ffffffffffffffda RBX: 00007f938bfd7780 RCX: 00007f938c067727 [ 26.554380] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000003 [ 26.554383] RBP: 00007fff49f05a10 R08: 0000000000000000 R09: 0000000000000000 [ 26.554386] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000 [ 26.554389] R13: 00007fff49f05b40 R14: 00007f938c207000 R15: 000055c148471d78 [ 26.554397] [ 26.554399] [ 26.575612] Allocated by task 100: [ 26.575871] kasan_save_stack+0x24/0x50 [ 26.576157] kasan_save_track+0x14/0x30 [ 26.576418] __kasan_kmalloc+0x7f/0x90 [ 26.576668] __kmalloc_noprof+0x281/0x6c0 [ 26.576938] nvme_alloc_ns+0x7f7/0x3170 [ 26.577206] nvme_scan_ns+0x508/0x880 [ 26.577449] async_run_entry_fn+0x8c/0x350 [ 26.577723] process_scheduled_works+0xb6f/0x1a00 [ 26.578034] worker_thread+0x4ad/0xb40 [ 26.578283] kthread+0x34f/0x450 [ 26.578501] ret_from_fork+0x563/0x800 [ 26.578752] ret_from_fork_asm+0x1a/0x30 [ 26.579012] [ 26.579124] Freed by task 237: [ 26.579335] kasan_save_stack+0x24/0x50 [ 26.579596] kasan_save_track+0x14/0x30 [ 26.579855] kasan_save_free_info+0x3a/0x60 [ 26.580131] __kasan_slab_free+0x43/0x70 [ 26.580388] kfree+0x321/0x500 [ 26.580591] nvme_ns_head_chr_release+0x39/0x50 [ 26.580883] __fput+0x352/0xa70 [ 26.581095] fput_close_sync+0xd8/0x190 [ 26.581350] __x64_sys_close+0x79/0xd0 [ 26.581595] do_syscall_64+0x117/0x6b0 [ 26.581842] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 26.582176] [ 26.582286] Last potentially related work creation: [ 26.582593] kasan_save_stack+0x24/0x50 [ 26.582841] kasan_record_aux_stack+0x89/0xa0 [ 26.583210] insert_work+0x22/0x170 [ 26.583442] __queue_work+0x7b1/0xfa0 [ 26.583682] queue_work_on+0x77/0x80 [ 26.583921] kblockd_schedule_work+0x18/0x20 [ 26.584207] nvme_mpath_put_disk+0x42/0xa0 [ 26.584632] nvme_free_ns_head+0x1c/0x160 [ 26.584904] nvme_ns_head_chr_release+0x39/0x50 [ 26.585208] __fput+0x352/0xa70 [ 26.585420] fput_close_sync+0xd8/0x190 [ 26.585677] __x64_sys_close+0x79/0xd0 [ 26.585924] do_syscall_64+0x117/0x6b0 [ 26.586173] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 26.586505] [ 26.586616] Second to last potentially related work creation: [ 26.586991] kasan_save_stack+0x24/0x50 [ 26.587246] kasan_record_aux_stack+0x89/0xa0 [ 26.587538] insert_work+0x22/0x170 [ 26.587770] __queue_work+0x7b1/0xfa0 [ 26.588010] queue_work_on+0x77/0x80 [ 26.588247] kblockd_schedule_work+0x18/0x20 [ 26.588529] nvme_remove_head+0x3d/0xb0 [ 26.588787] nvme_ns_remove+0x4b2/0x930 [ 26.589040] nvme_remove_namespaces+0x29c/0x410 [ 26.589340] nvme_do_delete_ctrl+0xf3/0x190 [ 26.589611] nvme_delete_ctrl_sync+0x71/0x90 [ 26.589889] nvme_sysfs_delete+0x91/0xb0 [ 26.590151] kernfs_fop_write_iter+0x2fb/0x4a0 [ 26.590452] vfs_write+0x929/0xfc0 [ 26.590688] ksys_write+0xf2/0x1d0 [ 26.590923] do_syscall_64+0x117/0x6b0 [ 26.591171] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 26.591498] [ 26.591605] The buggy address belongs to the object at ffff88811e7fa000 [ 26.591605] which belongs to the cache kmalloc-4k of size 4096 [ 26.592409] The buggy address is located 368 bytes inside of [ 26.592409] freed 4096-byte region [ffff88811e7fa000, ffff88811e7fb000) [ 26.593205] [ 26.593321] The buggy address belongs to the physical page: [ 26.593701] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11e7f8 [ 26.594250] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 [ 26.594743] flags: 0x200000000000040(head|node=0|zone=2) [ 26.595093] page_type: f5(slab) [ 26.595312] raw: 0200000000000040 ffff888100043040 dead000000000122 0000000000000000 [ 26.595810] raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000 [ 26.596309] head: 0200000000000040 ffff888100043040 dead000000000122 0000000000000000 [ 26.596806] head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000 [ 26.597313] head: 0200000000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff [ 26.597813] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 [ 26.598317] page dumped because: kasan: bad access detected [ 26.598676] [ 26.598784] Memory state around the buggy address: [ 26.599093] ffff88811e7fa000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 26.599559] ffff88811e7fa080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 26.600023] >ffff88811e7fa100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 26.600485] ^ [ 26.600921] ffff88811e7fa180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 26.601390] ffff88811e7fa200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 26.601855] ================================================================== [ 26.602374] Disabling lock debugging due to kernel taint When all fds for the cdev disappear, the cdev removal path puts a reference to the parent object, which is the nvme_ns_head.cdev_device - see cdev_default_release() -> kobject_put(parent). Fix the lifetime for the cdev by making adding the cdev add take a reference to the NS head and drop that reference in the nvme_ns_head.cdev_device release function. The same problem exists for the NS cdev lifetime, so resolve that issue through a similar method by taking a reference to the NS for the lifetime of the cdev. Note that nvme_ns_chr_open() -> nvme_ns_open() also takes a reference to the NS. Now that should not be needed, but that code is common to bdev ioctl, so keep as is. [0] https://lore.kernel.org/linux-scsi/20260703102918.3723667-1-john.g.garry@oracle.com/T/#m67265e2906d617acd2743c0a00809246d0cfc506 Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: John Garry Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 7 +++++++ drivers/nvme/host/multipath.c | 22 ++-------------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 882920b7327b..b7293fe66540 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3903,6 +3903,11 @@ static int nvme_subsys_check_duplicate_ids(struct nvme_subsystem *subsys, static void nvme_cdev_rel(struct device *dev) { ida_free(&nvme_ns_chr_minor_ida, MINOR(dev->devt)); + if (dev->parent->class == &nvme_class) + nvme_put_ns(container_of(dev, struct nvme_ns, cdev_device)); + else + nvme_put_ns_head(container_of(dev, struct nvme_ns_head, + cdev_device)); } void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device) @@ -3968,10 +3973,12 @@ static void nvme_add_ns_cdev(struct nvme_ns *ns) snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance, ns->head->instance); + nvme_get_ns(ns); /* Undone in nvme_cdev_rel() */ if (nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, &nvme_ns_chr_fops, ns->ctrl->ops->module)) { dev_err(ns->ctrl->device, "Unable to create the %s device\n", name); + nvme_put_ns(ns); return; } set_bit(NVME_NS_CDEV_LIVE, &ns->flags); diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 8cb417036fe1..c850a4bf7380 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -630,28 +630,8 @@ const struct block_device_operations nvme_ns_head_ops = { .pr_ops = &nvme_pr_ops, }; -static inline struct nvme_ns_head *cdev_to_ns_head(struct cdev *cdev) -{ - return container_of(cdev, struct nvme_ns_head, cdev); -} - -static int nvme_ns_head_chr_open(struct inode *inode, struct file *file) -{ - if (!nvme_tryget_ns_head(cdev_to_ns_head(inode->i_cdev))) - return -ENXIO; - return 0; -} - -static int nvme_ns_head_chr_release(struct inode *inode, struct file *file) -{ - nvme_put_ns_head(cdev_to_ns_head(inode->i_cdev)); - return 0; -} - static const struct file_operations nvme_ns_head_chr_fops = { .owner = THIS_MODULE, - .open = nvme_ns_head_chr_open, - .release = nvme_ns_head_chr_release, .unlocked_ioctl = nvme_ns_head_chr_ioctl, .compat_ioctl = compat_ptr_ioctl, .uring_cmd = nvme_ns_head_chr_uring_cmd, @@ -666,10 +646,12 @@ static void nvme_add_ns_head_cdev(struct nvme_ns_head *head) snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance, head->instance); + nvme_get_ns_head(head); /* Undone in nvme_cdev_rel() */ if (nvme_cdev_add(name, &head->cdev, &head->cdev_device, &nvme_ns_head_chr_fops, THIS_MODULE)) { dev_err(disk_to_dev(head->disk), "Unable to create the %s device\n", name); + nvme_put_ns_head(head); return; } set_bit(NVME_NSHEAD_CDEV_LIVE, &head->flags); From 737a3b535247226f6e1a7988fd9d6e63e7d6fc71 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 27 Jul 2026 22:03:31 +0200 Subject: [PATCH 25/81] nvmet-tcp: Do not WARN on remotely-controlled oversized SGL allocations When fuzzing the nvme target code, I tripped a kernel warning in nvmet_tcp_map_data() because the length passed into the allocator is controlled by the remote initiator. A remote initiator that sends a command with an SGL claiming a huge number, can create a scatterlist and iovec allocation of over 1 million entries, which causes the backing kmalloc call to exceed MAX_PAGE_ORDER and then the page allocator will trip on a WARN_ON_ONCE_GFP() message: WARNING: mm/page_alloc.c:5280 __alloc_frozen_pages_noprof Workqueue: nvmet_tcp_wq nvmet_tcp_io_work ... sgl_alloc_order nvmet_tcp_map_data nvmet_tcp_try_recv_pdu As it's never good to trip a kernel warning remotely due to many systems having panic-on-warn enabled, let's silence it by just add GFP_NOWARN to the allocation flags. Assisted-by: gkh_clanker_2000 Cc: stable Signed-off-by: Greg Kroah-Hartman Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 75a276d73be3..cb6d37798d74 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -433,13 +433,15 @@ static int nvmet_tcp_map_data(struct nvmet_tcp_cmd *cmd) } cmd->req.transfer_len += len; - cmd->req.sg = sgl_alloc(len, GFP_KERNEL, &cmd->req.sg_cnt); + cmd->req.sg = sgl_alloc(len, GFP_KERNEL | __GFP_NOWARN, + &cmd->req.sg_cnt); if (!cmd->req.sg) return NVME_SC_INTERNAL; cmd->cur_sg = cmd->req.sg; if (nvmet_tcp_has_data_in(cmd)) { - cmd->iov = kmalloc_objs(*cmd->iov, cmd->req.sg_cnt); + cmd->iov = kmalloc_objs(*cmd->iov, cmd->req.sg_cnt, + GFP_KERNEL | __GFP_NOWARN); if (!cmd->iov) goto err; } From a7609033629624fcbc2032431cbe8c4a84a3ac34 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:02 +0530 Subject: [PATCH 26/81] list: introduce LIST_HEAD_GUARDED Introduce LIST_HEAD_GUARDED(name, lock) to define a struct list_head annotated with __guarded_by(lock). This provides a convenient shorthand for defining lock-protected list heads and allows compiler context analysis to validate accesses to the list against the associated lock. The new helper also reduces boilerplate and improves consistency across callers that annotate struct list_head objects with __guarded_by(). This is a preparatory change for subsequent patches that annotate LIST_HEAD() instances with their protecting lock. Suggested-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- include/linux/list.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/linux/list.h b/include/linux/list.h index 09d979976b3b..f6f22c8b06f7 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -33,6 +33,14 @@ #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) +/** + * LIST_HEAD_GUARDED - define a &struct list_head annotated with __guarded_by() + * @name: name of the list_head + * @lock: lock protecting the list + */ +#define LIST_HEAD_GUARDED(name, lock) \ + __guarded_by(&(lock)) LIST_HEAD(name) + /** * INIT_LIST_HEAD - Initialize a list_head structure * @list: list_head structure to be initialized. From 2b58c94ea7ac6d26ee44b4734f7dc0e5d773ff70 Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Mon, 13 Jul 2026 17:24:03 +0530 Subject: [PATCH 27/81] list: Permit context-unguarded access with list_empty_careful() With Context Analysis (viz. Clang's Thread Safety Analysis), list_heads that are __guarded_by(..) require holding the appropriate context lock when accessing and manipulating them via the list API. Because Clang's warning diagnostics do not perform inter-procedural analysis, this is enforced by Clang with -Wthread-safety-pointer in the caller at the call boundary; a warning is produced when passing a pointer to a guarded variable without holding the appropriate context locks: warning: passing pointer to variable 'list' requires holding [...] [-Wthread-safety-pointer] if (list_empty(&ctrl->list)) An exception is list_empty_careful(), which is like list_empty(), except that it is permitted to use without holding any context lock (carefully). Mark list_empty_careful() __context_unsafe, which disables context analysis within list_empty_careful(), but also suppresses warnings generated in callers related to its pointer arguments. Reviewed-by: Christoph Hellwig Signed-off-by: Marco Elver Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- include/linux/list.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/list.h b/include/linux/list.h index f6f22c8b06f7..19212bfc3f6d 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -444,6 +444,7 @@ static inline void list_del_init_careful(struct list_head *entry) * if another CPU could re-list_add() it. */ static inline int list_empty_careful(const struct list_head *head) + __context_unsafe(/* intentional lockless access to @head */) { struct list_head *next = smp_load_acquire(&head->next); return list_is_head(next, head) && (next == READ_ONCE(head->prev)); From f6f7849c1655ff012d6396c408cf9d4712307fdb Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:04 +0530 Subject: [PATCH 28/81] nvme: update nvme_passthru_end() signature Change nvme_passthru_end() to return the command effects value passed to it. This is a preparatory change for Clang's context/thread-safety analysis support. The conditional release annotations (__cond_releases()) model lock release based on a function's return value. Returning the existing effects value allows a subsequent patch to annotate nvme_passthru_end() as conditionally releasing locks acquired by nvme_passthru_start(). No functional change intended. A follow-up patch will add the corresponding context analysis annotations. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 6 ++++-- drivers/nvme/host/nvme.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index b7293fe66540..9cb32beae028 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -1273,7 +1273,7 @@ u32 nvme_passthru_start(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode) } EXPORT_SYMBOL_NS_GPL(nvme_passthru_start, "NVME_TARGET_PASSTHRU"); -void nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, +u32 nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, struct nvme_command *cmd, int status) { if (effects & NVME_CMD_EFFECTS_CSE_MASK) { @@ -1294,7 +1294,7 @@ void nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, flush_work(&ctrl->scan_work); } if (ns) - return; + return effects; switch (cmd->common.opcode) { case nvme_admin_set_features: @@ -1315,6 +1315,8 @@ void nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, default: break; } + + return effects; } EXPORT_SYMBOL_NS_GPL(nvme_passthru_end, "NVME_TARGET_PASSTHRU"); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 2e9dea6420da..ebff6b45e976 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1302,7 +1302,7 @@ u32 nvme_command_effects(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode); u32 nvme_passthru_start(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode); int nvme_execute_rq(struct request *rq, bool at_head); -void nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, +u32 nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, struct nvme_command *cmd, int status); struct nvme_ctrl *nvme_ctrl_from_file(struct file *file); struct nvme_ns *nvme_find_get_ns(struct nvme_ctrl *ctrl, unsigned nsid); From a6732bd8003ad1ea9283c204b9ea6443d98bbc26 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:05 +0530 Subject: [PATCH 29/81] nvme: add context annotations for nvme_passthru_{start|stop} Annotate nvme_passthru_start() and nvme_passthru_end() for Clang context/thread-safety analysis. The __cond_acquires() and __cond_releases() annotations model conditional lock acquisition and release based on a function's return value. Use a nonzero return value as the abstract condition denoting that the associated locks have been acquired or released. This allows the analyzer to track the lock state across the nvme_passthru_start() / nvme_passthru_end() pair and verify correct locking semantics. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/nvme.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index ebff6b45e976..26859aea3e2d 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1300,10 +1300,16 @@ static inline void nvme_auth_revoke_tls_key(struct nvme_ctrl *ctrl) {}; u32 nvme_command_effects(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode); -u32 nvme_passthru_start(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode); +u32 nvme_passthru_start(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u8 opcode) + __cond_acquires(nonzero, &ctrl->subsys->lock) + __cond_acquires(nonzero, &ctrl->scan_lock); + int nvme_execute_rq(struct request *rq, bool at_head); u32 nvme_passthru_end(struct nvme_ctrl *ctrl, struct nvme_ns *ns, u32 effects, - struct nvme_command *cmd, int status); + struct nvme_command *cmd, int status) + __cond_releases(nonzero, &ctrl->scan_lock) + __cond_releases(nonzero, &ctrl->subsys->lock); + struct nvme_ctrl *nvme_ctrl_from_file(struct file *file); struct nvme_ns *nvme_find_get_ns(struct nvme_ctrl *ctrl, unsigned nsid); bool nvme_get_ns(struct nvme_ns *ns); From 86f9536c2d8f4496f1e45cb0a70ca1b3a7d89106 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:06 +0530 Subject: [PATCH 30/81] nvme: add context annotations for nvme_ns_head::srcu Add Clang lock context annotations for helpers that operate under head->srcu read-side protection. The path selection helpers invoked by nvme_find_path() access SRCU- protected data through srcu_dereference() or list APIs which iterate through rcu protected list and therefore require the caller to hold head->srcu. Annotate these helpers and nvme_find_path() with __must_hold_shared(&head->srcu) so that Clang's lock context analysis can verify the SRCU locking requirements across the call chain. Also update nvme_ns_head_ctrl_ioctl() to use __releases_shared() to match the shared SRCU read-side lock acquired through srcu_read_lock(). Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/ioctl.c | 2 +- drivers/nvme/host/multipath.c | 6 ++++++ drivers/nvme/host/nvme.h | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c index d5a8f375953b..bae52bd5bdd2 100644 --- a/drivers/nvme/host/ioctl.c +++ b/drivers/nvme/host/ioctl.c @@ -692,7 +692,7 @@ int nvme_ns_chr_uring_cmd_iopoll(struct io_uring_cmd *ioucmd, static int nvme_ns_head_ctrl_ioctl(struct nvme_ns *ns, unsigned int cmd, void __user *argp, struct nvme_ns_head *head, int srcu_idx, bool open_for_write) - __releases(&head->srcu) + __releases_shared(&head->srcu) { struct nvme_ctrl *ctrl = ns->ctrl; int ret; diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index c850a4bf7380..b9bb9777da96 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -325,6 +325,7 @@ static bool nvme_path_is_disabled(struct nvme_ns *ns) } static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) + __must_hold_shared(&head->srcu) { int found_distance = INT_MAX, fallback_distance = INT_MAX, distance; struct nvme_ns *found = NULL, *fallback = NULL, *ns; @@ -367,6 +368,7 @@ static struct nvme_ns *__nvme_find_path(struct nvme_ns_head *head, int node) static struct nvme_ns *nvme_next_ns(struct nvme_ns_head *head, struct nvme_ns *ns) + __must_hold_shared(&head->srcu) { ns = list_next_or_null_rcu(&head->list, &ns->siblings, struct nvme_ns, siblings); @@ -376,6 +378,7 @@ static struct nvme_ns *nvme_next_ns(struct nvme_ns_head *head, } static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) + __must_hold_shared(&head->srcu) { struct nvme_ns *ns, *found = NULL; int node = numa_node_id(); @@ -424,6 +427,7 @@ static struct nvme_ns *nvme_round_robin_path(struct nvme_ns_head *head) } static struct nvme_ns *nvme_queue_depth_path(struct nvme_ns_head *head) + __must_hold_shared(&head->srcu) { struct nvme_ns *best_opt = NULL, *best_nonopt = NULL, *ns; unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX; @@ -467,6 +471,7 @@ static inline bool nvme_path_is_optimized(struct nvme_ns *ns) } static struct nvme_ns *nvme_numa_path(struct nvme_ns_head *head) + __must_hold_shared(&head->srcu) { int node = numa_node_id(); struct nvme_ns *ns; @@ -492,6 +497,7 @@ inline struct nvme_ns *nvme_find_path(struct nvme_ns_head *head) } static bool nvme_available_path(struct nvme_ns_head *head) + __must_hold_shared(&head->srcu) { struct nvme_ns *ns; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 26859aea3e2d..ec9dea4d7fb9 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1033,7 +1033,8 @@ extern const struct attribute_group *nvme_dev_attr_groups[]; extern const struct block_device_operations nvme_bdev_ops; void nvme_delete_ctrl_sync(struct nvme_ctrl *ctrl); -struct nvme_ns *nvme_find_path(struct nvme_ns_head *head); +struct nvme_ns *nvme_find_path(struct nvme_ns_head *head) + __must_hold_shared(&head->srcu); #ifdef CONFIG_NVME_MULTIPATH static inline bool nvme_ctrl_use_ana(struct nvme_ctrl *ctrl) { From 499d05d5d10eb381eac87a83626e7141a5823a09 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:07 +0530 Subject: [PATCH 31/81] nvme: remove redundant initialization of nvme_ns_head::requeue_list bio_list_init() is a no-op for zero-initialized objects. Remove the redundant initialization of nvme_ns_head::requeue_list from nvme_mpath_alloc_disk(). Besides simplifying the code, this also avoids a false positive from Clang's context analysis once nvme_ns_head::requeue_list is annotated with __guarded_by(&requeue_lock). Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index b9bb9777da96..fac6ea2311c1 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -736,7 +736,6 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) struct queue_limits lim; mutex_init(&head->lock); - bio_list_init(&head->requeue_list); spin_lock_init(&head->requeue_lock); INIT_WORK(&head->requeue_work, nvme_requeue_work); INIT_WORK(&head->partition_scan_work, nvme_partition_scan_work); From aa5d8dda3a455c8b0c06a9ab360a52c6a6fae0b6 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:08 +0530 Subject: [PATCH 32/81] nvme: add context annotations for nvme_ns_head::requeue_list nvme_ns_head::requeue_list is protected by nvme_ns_head::requeue_lock. Annotate requeue_list with __guarded_by(&requeue_lock) so that Clang's context analysis can validate accesses to the list. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/nvme.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index ec9dea4d7fb9..27023648cbd1 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -561,7 +561,8 @@ struct nvme_ns_head { u16 nr_plids; u16 *plids; #ifdef CONFIG_NVME_MULTIPATH - struct bio_list requeue_list; + struct bio_list requeue_list + __guarded_by(&requeue_lock); spinlock_t requeue_lock; struct work_struct requeue_work; struct work_struct partition_scan_work; From 696d2aeb77513eb474eb3557efc32be763289e4f Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:09 +0530 Subject: [PATCH 33/81] nvme: add context annotations for nvme_ns_head::current_path Annotate nvme_ns_head::current_path[] with __rcu_guarded so that Clang's context analysis can validate accesses to the SRCU/RCU protected pointer. Cc: Paul E. McKenney Reviewed-by: Paul E. McKenney Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/nvme.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 27023648cbd1..51221ba0f1ad 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -575,7 +575,7 @@ struct nvme_ns_head { #define NVME_NSHEAD_DISK_LIVE 0 #define NVME_NSHEAD_QUEUE_IF_NO_PATH 1 #define NVME_NSHEAD_CDEV_LIVE 2 - struct nvme_ns __rcu *current_path[]; + struct nvme_ns __rcu_guarded *current_path[]; #endif }; From 4258bf237e7f26cbbd44c9dada11b87cda18041c Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:10 +0530 Subject: [PATCH 34/81] nvme: add context annotations for nvme_dev::shutdown_lock nvme_setup_io_queues_trylock() conditionally acquires dev->shutdown_lock using mutex_trylock(). The function returns 0 when the lock is successfully acquired and a negative error code otherwise. Annotate the function with __cond_acquires(0, &dev->shutdown_lock) so that Clang's lock context analysis can track the lock state based on the return value and verify correct lock usage at call sites. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 8438c904ec49..8c6d169f2c38 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2199,6 +2199,7 @@ static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid) * Try getting shutdown_lock while setting up IO queues. */ static int nvme_setup_io_queues_trylock(struct nvme_dev *dev) + __cond_acquires(0, &dev->shutdown_lock) { /* * Give up if the lock is being held by nvme_dev_disable. From 9c65eeeb26b1d614787deec36faec81e45b8f8e8 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:11 +0530 Subject: [PATCH 35/81] nvme: remove redundant initialization of delayed_removal_secs nvme_ns_head is allocated with kzalloc(), so explicitly initializing nvme_ns_head::delayed_removal_secs to 0 in nvme_mpath_alloc_disk() is redundant. Removing the redundant initialization also avoids a false positive from Clang's context analysis once nvme_ns_head::delayed_removal_secs is annotated with __guarded_by(nvme_subsystem::lock). Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index fac6ea2311c1..091aceb9b1d8 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -740,7 +740,6 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) INIT_WORK(&head->requeue_work, nvme_requeue_work); INIT_WORK(&head->partition_scan_work, nvme_partition_scan_work); INIT_DELAYED_WORK(&head->remove_work, nvme_remove_head_work); - head->delayed_removal_secs = 0; /* * If "multipath_always_on" is enabled, a multipath node is added From d1fdf49b5f7fce5f65ae0d11d484bd7e31cedbb1 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:12 +0530 Subject: [PATCH 36/81] nvme: add context annotations for nvme_subsystem::lock Several helpers access or traverse data structures protected by nvme_subsystem::lock and therefore require callers to hold the lock. Annotate nvme_mpath_unfreeze(), nvme_mpath_wait_freeze(), nvme_mpath_start_freeze(), nvme_find_ns_head(), nvme_alloc_ns_head() and nvme_subsys_check_duplicate_ids() with __must_hold(&subsys->lock) so that Clang's lock context analysis can validate the locking requirements at compile time. Also annotate nvme_subsystem::nsheads and nvme_ns_head::delayed_removal_secs with __guarded_by(&subsys->lock), as both are protected by the subsystem lock. Annotate nvme_init_subsystem() with __context_unsafe(), as it initializes these lock-protected members before the object is published, suppressing a false positive from Clang's context analysis. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 ++++ drivers/nvme/host/nvme.h | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 9cb32beae028..178ac655aa2b 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3287,6 +3287,7 @@ static bool nvme_validate_cntlid(struct nvme_subsystem *subsys, } static int nvme_init_subsystem(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) + __context_unsafe(/* initialize unpublished/lock-guarded variables */) { struct nvme_subsystem *subsys, *found; int ret; @@ -3858,6 +3859,7 @@ static const struct file_operations nvme_dev_fops = { static struct nvme_ns_head *nvme_find_ns_head(struct nvme_ctrl *ctrl, unsigned nsid) + __must_hold(&ctrl->subsys->lock) { struct nvme_ns_head *h; @@ -3880,6 +3882,7 @@ static struct nvme_ns_head *nvme_find_ns_head(struct nvme_ctrl *ctrl, static int nvme_subsys_check_duplicate_ids(struct nvme_subsystem *subsys, struct nvme_ns_ids *ids) + __must_hold(&subsys->lock) { bool has_uuid = !uuid_is_null(&ids->uuid); bool has_nguid = memchr_inv(ids->nguid, 0, sizeof(ids->nguid)); @@ -3988,6 +3991,7 @@ static void nvme_add_ns_cdev(struct nvme_ns *ns) static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, struct nvme_ns_info *info) + __must_hold(&ctrl->subsys->lock) { struct nvme_ns_head *head; size_t size = sizeof(*head); diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 51221ba0f1ad..fac4acbbd85d 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -496,7 +496,8 @@ struct nvme_subsystem { struct list_head entry; struct mutex lock; struct list_head ctrls; - struct list_head nsheads; + struct list_head nsheads + __guarded_by(&lock); char subnqn[NVMF_NQN_SIZE]; char serial[20]; char model[40]; @@ -569,7 +570,8 @@ struct nvme_ns_head { struct mutex lock; unsigned long flags; struct delayed_work remove_work; - unsigned int delayed_removal_secs; + unsigned int delayed_removal_secs + __guarded_by(&subsys->lock); atomic_long_t io_requeue_no_usable_path_count; atomic_long_t io_fail_no_available_path_count; #define NVME_NSHEAD_DISK_LIVE 0 @@ -1042,9 +1044,12 @@ static inline bool nvme_ctrl_use_ana(struct nvme_ctrl *ctrl) return ctrl->ana_log_buf != NULL; } -void nvme_mpath_unfreeze(struct nvme_subsystem *subsys); -void nvme_mpath_wait_freeze(struct nvme_subsystem *subsys); -void nvme_mpath_start_freeze(struct nvme_subsystem *subsys); +void nvme_mpath_unfreeze(struct nvme_subsystem *subsys) + __must_hold(&subsys->lock); +void nvme_mpath_wait_freeze(struct nvme_subsystem *subsys) + __must_hold(&subsys->lock); +void nvme_mpath_start_freeze(struct nvme_subsystem *subsys) + __must_hold(&subsys->lock); void nvme_mpath_default_iopolicy(struct nvme_subsystem *subsys); void nvme_failover_req(struct request *req); void nvme_kick_requeue_lists(struct nvme_ctrl *ctrl); From ca0058e8b599ae75a30e7f53025a46b62905bea3 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:13 +0530 Subject: [PATCH 37/81] nvme: add context annotations for nvme_ctrl::ana_lock nvme_parse_ana_log() accesses ANA state protected by ctrl->ana_lock and therefore requires callers to hold the lock. Annotate nvme_parse_ana_log() with __must_hold(&ctrl->ana_lock) so that Clang's lock context analysis can verify the locking requirement at compile time. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 091aceb9b1d8..75dbb58286a3 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -832,6 +832,7 @@ static void nvme_mpath_set_live(struct nvme_ns *ns) static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, int (*cb)(struct nvme_ctrl *ctrl, struct nvme_ana_group_desc *, void *)) + __must_hold(&ctrl->ana_lock) { void *base = ctrl->ana_log_buf; size_t offset = sizeof(struct nvme_ana_rsp_hdr); From 8aa68dba25f53f011ea39939af76171d4bf481ea Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:14 +0530 Subject: [PATCH 38/81] nvme: add context annotations for nvme_subsystems_lock The global nvme_subsystems list, nvme_subsystem::entry, nvme_subsystem::ctrls, and nvme_ctrl::subsys_entry are protected by nvme_subsystems_lock. Annotate these objects with __guarded_by(&nvme_subsystems_lock) so that Clang's context analysis can validate accesses to them. __nvme_find_get_subsystem() and nvme_validate_cntlid() traverse the global subsystem list and subsystem controller list and therefore require callers to hold nvme_subsystems_lock. Annotate both helpers with __must_hold(&nvme_subsystems_lock). Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 4 +++- drivers/nvme/host/nvme.h | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 178ac655aa2b..cb93ada4376a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -126,8 +126,8 @@ EXPORT_SYMBOL_GPL(nvme_reset_wq); struct workqueue_struct *nvme_delete_wq; EXPORT_SYMBOL_GPL(nvme_delete_wq); -static LIST_HEAD(nvme_subsystems); DEFINE_MUTEX(nvme_subsystems_lock); +static LIST_HEAD_GUARDED(nvme_subsystems, nvme_subsystems_lock); static DEFINE_IDA(nvme_instance_ida); static dev_t nvme_ctrl_base_chr_devt; @@ -3213,6 +3213,7 @@ static void nvme_put_subsystem(struct nvme_subsystem *subsys) } static struct nvme_subsystem *__nvme_find_get_subsystem(const char *subsysnqn) + __must_hold(&nvme_subsystems_lock) { struct nvme_subsystem *subsys; @@ -3257,6 +3258,7 @@ static inline bool nvme_is_io_ctrl(struct nvme_ctrl *ctrl) static bool nvme_validate_cntlid(struct nvme_subsystem *subsys, struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) + __must_hold(&nvme_subsystems_lock) { struct nvme_ctrl *tmp; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index fac4acbbd85d..862464301d01 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -361,7 +361,8 @@ struct nvme_ctrl { wait_queue_head_t state_wq; struct nvme_subsystem *subsys; - struct list_head subsys_entry; + struct list_head subsys_entry + __guarded_by(&nvme_subsystems_lock); struct opal_dev *opal_dev; @@ -493,9 +494,11 @@ struct nvme_subsystem { * a separate refcount. */ struct kref ref; - struct list_head entry; + struct list_head entry + __guarded_by(&nvme_subsystems_lock); struct mutex lock; - struct list_head ctrls; + struct list_head ctrls + __guarded_by(&nvme_subsystems_lock); struct list_head nsheads __guarded_by(&lock); char subnqn[NVMF_NQN_SIZE]; From 50be6cb15f15006477332a20c4a4adba59a55163 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:15 +0530 Subject: [PATCH 39/81] nvme: add context annotations in fabric.c The global nvmf_transports list is protected by nvmf_transports_rwsem and the global nvmf_hosts list is protected by nvmf_hosts_mutex. Define both lists using LIST_HEAD_GUARDED() so that Clang's context analysis can validate accesses to the lists against the corresponding locking requirements. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/fabrics.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index ac3d4f400601..fd5abd04e080 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -14,11 +14,11 @@ #include "fabrics.h" #include -static LIST_HEAD(nvmf_transports); static DECLARE_RWSEM(nvmf_transports_rwsem); +static LIST_HEAD_GUARDED(nvmf_transports, nvmf_transports_rwsem); -static LIST_HEAD(nvmf_hosts); static DEFINE_MUTEX(nvmf_hosts_mutex); +static LIST_HEAD_GUARDED(nvmf_hosts, nvmf_hosts_mutex); static struct nvmf_host *nvmf_default_host; From 1f3d29bdca645edd5a623639604328b8ec2193d6 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:16 +0530 Subject: [PATCH 40/81] nvme: add context annotations for nvme_queue::sq_lock nvme_queue::sq_tail, nvme_queue::last_sq_tail and nvme_queue::sq_cmds are protected by nvme_queue::sq_lock. Annotate each field with __guarded_by(&sq_lock) and annotate helpers that access them with __must_hold(&sq_lock) so that Clang's context analysis can validate the locking requirements. Access to nvme_queue::sq_tail used solely for tracing is annotated with data_race(), as they only require a lockless snapshot of the value. nvme_init_queue() initializes nvme_queue::sq_tail and nvme_queue::last_sq_tail before the queue is published and thus do not require nvme_queue::sq_lock protection. So annotate nvme_init_queue() with context_unsafe() to suppress false positive context analyzer warning. nvme_free_queue() operate on queues which are no longer reachable, and therefore do not require nvme_queue::sq_lock protection. Similarly, nvme_alloc_sq_cmds() allocates memory for nvme_queue::sq_cmds for the queue which is not yet published or in use and hence it's safe to annotate all these helpers using context_unsafe. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 8c6d169f2c38..0bce364c7874 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -366,7 +366,8 @@ struct nvme_queue { struct nvme_dev *dev; struct nvme_descriptor_pools descriptor_pools; spinlock_t sq_lock; - void *sq_cmds; + void *sq_cmds + __guarded_by(&sq_lock); /* only used for poll queues: */ spinlock_t cq_poll_lock ____cacheline_aligned_in_smp; struct nvme_completion *cqes; @@ -375,9 +376,11 @@ struct nvme_queue { u32 __iomem *q_db; u32 q_depth; u16 cq_vector; - u16 sq_tail; - u16 last_sq_tail; u16 cq_head; + u16 sq_tail + __guarded_by(&sq_lock); + u16 last_sq_tail + __guarded_by(&sq_lock); u16 qid; u8 cq_phase; u8 sqes; @@ -716,6 +719,7 @@ static void nvme_pci_map_queues(struct blk_mq_tag_set *set) * Write sq tail if we are asked to, or if the next command would wrap. */ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq) + __must_hold(&nvmeq->sq_lock) { if (!write_sq) { u16 next_tail = nvmeq->sq_tail + 1; @@ -734,6 +738,7 @@ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq) static inline void nvme_sq_copy_cmd(struct nvme_queue *nvmeq, struct nvme_command *cmd) + __must_hold(&nvmeq->sq_lock) { memcpy(nvmeq->sq_cmds + (nvmeq->sq_tail << nvmeq->sqes), absolute_pointer(cmd), sizeof(*cmd)); @@ -1586,7 +1591,12 @@ static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, return; } - trace_nvme_sq(req, cqe->sq_head, nvmeq->sq_tail); + /* + * Tracing only; annotate a lockless snapshot of nvmeq->sq_tail using + * data_race(). This would also help suppress context analysis warning + * while accessing nvmeq->sq_tail without acquiring ->sq_lock. + */ + trace_nvme_sq(req, cqe->sq_head, data_race(nvmeq->sq_tail)); if (!nvme_try_complete_req(req, cqe->status, cqe->result) && !blk_mq_add_to_batch(req, iob, nvme_req(req)->status != NVME_SC_SUCCESS, @@ -2013,6 +2023,7 @@ static enum blk_eh_timer_return nvme_timeout(struct request *req) } static void nvme_free_queue(struct nvme_queue *nvmeq) + __context_unsafe(/* frees queue which is no longer in use */) { dma_free_coherent(nvmeq->dev->dev, CQ_SIZE(nvmeq), (void *)nvmeq->cqes, nvmeq->cq_dma_addr); @@ -2107,6 +2118,7 @@ static int nvme_cmb_qdepth(struct nvme_dev *dev, int nr_io_queues, static int nvme_alloc_sq_cmds(struct nvme_dev *dev, struct nvme_queue *nvmeq, int qid) + __context_unsafe(/* safe to allocate sq_cmds without any protection */) { struct pci_dev *pdev = to_pci_dev(dev->dev); @@ -2181,6 +2193,7 @@ static int queue_request_irq(struct nvme_queue *nvmeq) } static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid) + __context_unsafe(/* initialize unpublished/lock-guarded variables */) { struct nvme_dev *dev = nvmeq->dev; From 5de3b73cea44d2c5be7ae9ba9602755897ac2588 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:17 +0530 Subject: [PATCH 41/81] nvme: add context annotations in rdma.c device_list and nvme_rdma_device::entry are protected by device_list_mutex. Define device_list using LIST_HEAD_GUARDED(device_list, device_list_mutex) and annotate nvme_rdma_device::entry with __guarded_by(&device_list_mutex) so that Clang's context analysis can validate accesses against the corresponding locking requirements. Similarly, nvme_rdma_ctrl_list and nvme_rdma_ctrl::list are protected by nvme_rdma_ctrl_mutex. Define nvme_rdma_ctrl_list using LIST_HEAD_GUARDED(nvme_rdma_ctrl_list, nvme_rdma_ctrl_mutex) and annotate nvme_rdma_ctrl::list with __guarded_by(&nvme_rdma_ctrl_mutex). It is safe to initialize nvme_rdma_ctrl::list while allocating the controller object because the list entry has not yet been added to nvme_rdma_ctrl_list. Annotate the initialization with context_unsafe() to suppress the corresponding Clang context analysis warning. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 52933d11ea03..9111d58f9871 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -40,11 +40,18 @@ #define NVME_RDMA_METADATA_SGL_SIZE \ (sizeof(struct scatterlist) * NVME_INLINE_METADATA_SG_CNT) +static DEFINE_MUTEX(device_list_mutex); +static LIST_HEAD_GUARDED(device_list, device_list_mutex); + +static DEFINE_MUTEX(nvme_rdma_ctrl_mutex); +static LIST_HEAD_GUARDED(nvme_rdma_ctrl_list, nvme_rdma_ctrl_mutex); + struct nvme_rdma_device { struct ib_device *dev; struct ib_pd *pd; struct kref ref; - struct list_head entry; + struct list_head entry + __guarded_by(&device_list_mutex); unsigned int num_inline_segments; }; @@ -118,7 +125,8 @@ struct nvme_rdma_ctrl { struct delayed_work reconnect_work; - struct list_head list; + struct list_head list + __guarded_by(&nvme_rdma_ctrl_mutex); struct blk_mq_tag_set admin_tag_set; struct nvme_rdma_device *device; @@ -138,12 +146,6 @@ static inline struct nvme_rdma_ctrl *to_rdma_ctrl(struct nvme_ctrl *ctrl) return container_of(ctrl, struct nvme_rdma_ctrl, ctrl); } -static LIST_HEAD(device_list); -static DEFINE_MUTEX(device_list_mutex); - -static LIST_HEAD(nvme_rdma_ctrl_list); -static DEFINE_MUTEX(nvme_rdma_ctrl_mutex); - /* * Disabling this option makes small I/O goes faster, but is fundamentally * unsafe. With it turned off we will have to register a global rkey that @@ -2283,7 +2285,10 @@ static struct nvme_rdma_ctrl *nvme_rdma_alloc_ctrl(struct device *dev, if (!ctrl) return ERR_PTR(-ENOMEM); ctrl->ctrl.opts = opts; - INIT_LIST_HEAD(&ctrl->list); + /* + * Safe to init list while allocating ctrl object. + */ + context_unsafe(INIT_LIST_HEAD(&ctrl->list)); if (!(opts->mask & NVMF_OPT_TRSVCID)) { opts->trsvcid = From e906dc2a33de221b4cb2b2b7ba2e03835f3ee40e Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:18 +0530 Subject: [PATCH 42/81] nvme: fix context analysis warning in rdma.c After adding Clang lock context annotations in rdma.c, Clang reports the following warning when context analysis is enabled: drivers/nvme/host/rdma.c:972:24: warning: passing pointer to variable 'list' requires holding mutex 'nvme_rdma_ctrl_mutex' [-Wthread-safety-pointer] 972 | if (list_empty(&ctrl->list)) | ^ The warning is triggered because ctrl->list is annotated as being protected by nvme_rdma_ctrl_mutex, but list_empty(&ctrl->list) is invoked without holding that mutex. Replace list_empty() with list_empty_careful(), which is intended for lockless inspection of list heads during teardown when no concurrent list modifications are expected. This suppresses the corresponding context analysis warning while preserving the existing behavior. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 9111d58f9871..01743ae01466 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -1000,7 +1000,7 @@ static void nvme_rdma_free_ctrl(struct nvme_ctrl *nctrl) { struct nvme_rdma_ctrl *ctrl = to_rdma_ctrl(nctrl); - if (list_empty(&ctrl->list)) + if (list_empty_careful(&ctrl->list)) goto free_ctrl; mutex_lock(&nvme_rdma_ctrl_mutex); From 27a75a6290d610b40e4ef8024bef2acf7e3268ce Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:19 +0530 Subject: [PATCH 43/81] nvme: add context annotations in tcp.c The nvme_tcp_ctrl_list and nvme_tcp_ctrl::list are protected by nvme_tcp_ctrl_mutex. Define nvme_tcp_ctrl_list using LIST_HEAD_GUARDED(nvme_tcp_ctrl_list, nvme_tcp_ctrl_mutex) and annotate nvme_tcp_ctrl::list using __guarded_by(&nvme_tcp_ctrl_mutex) so that Clang's context analysis can validate accesses against the corresponding locking requirements. It is safe to initialize nvme_tcp_ctrl::list while allocating the controller object because the list entry has not yet been added to nvme_tcp_ctrl_list. Annotate the initialization with context_unsafe() to suppress the corresponding Clang warning. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index ba5c7b3e2a7c..8d2fbfc7cd8d 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -149,13 +149,17 @@ struct nvme_tcp_queue { #endif }; +static DEFINE_MUTEX(nvme_tcp_ctrl_mutex); +static LIST_HEAD_GUARDED(nvme_tcp_ctrl_list, nvme_tcp_ctrl_mutex); + struct nvme_tcp_ctrl { /* read only in the hot path */ struct nvme_tcp_queue *queues; struct blk_mq_tag_set tag_set; /* other member variables */ - struct list_head list; + struct list_head list + __guarded_by(&nvme_tcp_ctrl_mutex); struct blk_mq_tag_set admin_tag_set; struct sockaddr_storage addr; struct sockaddr_storage src_addr; @@ -167,8 +171,6 @@ struct nvme_tcp_ctrl { u32 io_queues[HCTX_MAX_TYPES]; }; -static LIST_HEAD(nvme_tcp_ctrl_list); -static DEFINE_MUTEX(nvme_tcp_ctrl_mutex); static struct workqueue_struct *nvme_tcp_wq; static const struct blk_mq_ops nvme_tcp_mq_ops; static const struct blk_mq_ops nvme_tcp_admin_mq_ops; @@ -2919,7 +2921,10 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, if (!ctrl) return ERR_PTR(-ENOMEM); - INIT_LIST_HEAD(&ctrl->list); + /* + * Safe to init list while allocating ctrl object. + */ + context_unsafe(INIT_LIST_HEAD(&ctrl->list)); ctrl->ctrl.opts = opts; ctrl->ctrl.queue_count = opts->nr_io_queues + opts->nr_write_queues + opts->nr_poll_queues + 1; From 521b1587de93650950d59b59b058bf5c24230973 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:20 +0530 Subject: [PATCH 44/81] nvme: fix context analysis warning in tcp.c After adding Clang context annotations, compiling tcp.c reports the following warning while context analysis is enabled: drivers/nvme/host/tcp.c:2572:24: warning: passing pointer to variable 'list' requires holding mutex 'nvme_tcp_ctrl_mutex' [-Wthread-safety-pointer] 2572 | if (list_empty(&ctrl->list)) | ^ The above warning is triggered because ctrl->list is guarded with mutex nvme_tcp_ctrl_mutex but when list_empty(&ctrl->list) is invoked it doesn't acquire nvme_tcp_ctrl_mutex. Replace list_empty() with list_empty_careful(), which is intended for lockless inspection of list heads during teardown when no concurrent list modifications are expected. This suppresses the corresponding Clang context analysis warning while preserving the existing behavior. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 8d2fbfc7cd8d..87d8067f3283 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -2579,7 +2579,7 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl) { struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl); - if (list_empty(&ctrl->list)) + if (list_empty_careful(&ctrl->list)) goto free_ctrl; mutex_lock(&nvme_tcp_ctrl_mutex); From fccada336f6d29344e3853d44b96684807dd7d7d Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Mon, 13 Jul 2026 17:24:21 +0530 Subject: [PATCH 45/81] nvme: enable context analysis support for nvme host driver Update nvme host driver makefile to enable support for the Clang's context anaysis. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile index 6414ec968f99..67563a69f7dc 100644 --- a/drivers/nvme/host/Makefile +++ b/drivers/nvme/host/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y ccflags-y += -I$(src) obj-$(CONFIG_NVME_CORE) += nvme-core.o From 08660a5c8d497f43191635d97efd31cd35051f15 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 15 Jul 2026 16:44:59 +0900 Subject: [PATCH 46/81] nvme-pci: disable controller on admin queue IRQ setup failure nvme_pci_configure_admin_queue() enables the controller and then requests the admin queue interrupt. If queue_request_irq() fails it returns without disabling the controller, and no caller compensates: nvme_pci_enable() only frees the IRQ vectors and calls pci_disable_device(), after which nvme_dev_disable() treats the controller as dead and skips nvme_disable_ctrl(). The controller is left enabled (CC.EN set) on this error path. Disable it in the failure path, while the PCI device is still enabled so the CC.EN clear handshake completes. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: b60503ba432b ("NVMe: New driver") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 0bce364c7874..16d42e5138c8 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -2414,6 +2414,7 @@ static int nvme_pci_configure_admin_queue(struct nvme_dev *dev) result = queue_request_irq(nvmeq); if (result) { dev->online_queues--; + nvme_disable_ctrl(&dev->ctrl, false); return result; } From 13330446caef56de008ecb9ad2a1545ce2fedd0a Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 16 Jul 2026 11:33:01 +0800 Subject: [PATCH 47/81] nvme-apple: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() calls. Reviewed-by: Christoph Hellwig Signed-off-by: Pan Chuang Signed-off-by: Keith Busch --- drivers/nvme/host/apple.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 2723bc1a7d8a..09eb2295ceee 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -1583,10 +1583,8 @@ static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev) ret = devm_request_irq(anv->dev, anv->irq, apple_nvme_irq, 0, "nvme-apple", anv); - if (ret) { - dev_err_probe(dev, ret, "Failed to request IRQ"); + if (ret) goto put_dev; - } anv->rtk = devm_apple_rtkit_init(dev, anv, NULL, 0, &apple_nvme_rtkit_ops); From b53d495c7f0db46b6748b5ade48371a10dd5d3bc Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Mon, 20 Jul 2026 14:03:05 +0800 Subject: [PATCH 48/81] nvme/ioctl: check SUBMIT_IO with nvme_cmd_allowed() Unlike IO_CMD / IO64_CMD, NVME_IOCTL_SUBMIT_IO never calls nvme_cmd_allowed(). Unprivileged callers can thus issue I/O on a partition device or write through a read-only file descriptor. Pass flags and open_for_write through and reject disallowed commands with -EACCES. Reviewed-by: Christoph Hellwig Signed-off-by: Yang Xiuwei Signed-off-by: Keith Busch --- drivers/nvme/host/ioctl.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c index bae52bd5bdd2..f4ea52d11945 100644 --- a/drivers/nvme/host/ioctl.c +++ b/drivers/nvme/host/ioctl.c @@ -202,7 +202,8 @@ static int nvme_submit_user_cmd(struct request_queue *q, return ret; } -static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) +static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio, + unsigned int flags, bool open_for_write) { struct nvme_user_io io; struct nvme_command c; @@ -260,6 +261,9 @@ static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio) c.rw.lbat = cpu_to_le16(io.apptag); c.rw.lbatm = cpu_to_le16(io.appmask); + if (!nvme_cmd_allowed(ns, &c, flags, open_for_write)) + return -EACCES; + return nvme_submit_user_cmd(ns->queue, &c, io.addr, length, metadata, meta_len, NULL, 0, 0); } @@ -595,7 +599,7 @@ static int nvme_ns_ioctl(struct nvme_ns *ns, unsigned int cmd, case NVME_IOCTL_SUBMIT_IO32: #endif case NVME_IOCTL_SUBMIT_IO: - return nvme_submit_io(ns, argp); + return nvme_submit_io(ns, argp, flags, open_for_write); case NVME_IOCTL_IO64_CMD_VEC: flags |= NVME_IOCTL_VEC; fallthrough; From 581d8bb556dd3e5567bcf322aa5e3e4b6a200c08 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 29 Jul 2026 18:56:07 +0800 Subject: [PATCH 49/81] nvmet: fix return status of RMI log page on allocation failure nvmet_execute_get_log_page_rmi() leaves 'status' holding NVME_SC_SUCCESS (set by the successful nvmet_req_find_ns() call) when the kzalloc() for the log buffer fails. It then jumps to the out label and completes the request with a success status, so the host is told the command succeeded while no data was transferred. Initialize 'status' to NVME_SC_INTERNAL, matching the smart log handler, so an allocation failure is reported as an internal error. Fixes: 5fd075cdaf36 ("nvmet: implement rotational media information log") Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/admin-cmd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c index 01b799e92ae6..0b24d31f966d 100644 --- a/drivers/nvme/target/admin-cmd.c +++ b/drivers/nvme/target/admin-cmd.c @@ -309,8 +309,10 @@ static void nvmet_execute_get_log_page_rmi(struct nvmet_req *req) } log = kzalloc_obj(*log); - if (!log) + if (!log) { + status = NVME_SC_INTERNAL; goto out; + } log->endgid = req->cmd->get_log_page.lsi; disk = req->ns->bdev->bd_disk; From f49d0c3a8d56a7cda1628ae17341a4a42063563c Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 29 Jul 2026 18:58:46 +0800 Subject: [PATCH 50/81] nvme-fc: unmap cmd_iu DMA on rsp_iu mapping failure in init_request __nvme_fc_init_request() maps cmd_iu and then rsp_iu for DMA. If the rsp_iu mapping fails, the original code only recorded the error and fell through: it left the already-mapped cmd_iu unmapped and still marked the op as FCPOP_STATE_IDLE before returning. Since blk-mq does not call .exit_request() when .init_request() fails, the cmd_iu mapping is leaked for every op whose rsp_iu mapping fails. Jump to an error path on rsp_iu mapping failure that unmaps cmd_iu and returns the error without marking the op idle, so it stays in the FCPOP_STATE_UNINIT state set by the initial memset(). Fixes: e399441de911 ("nvme-fabrics: Add host support for FC transport") Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 04363b9c4489..40f9da2833ff 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2100,9 +2100,15 @@ __nvme_fc_init_request(struct nvme_fc_ctrl *ctrl, dev_err(ctrl->dev, "FCP Op failed - rspiu dma mapping failed.\n"); ret = -EFAULT; + goto out_unmap; } atomic_set(&op->state, FCPOP_STATE_IDLE); + return 0; + +out_unmap: + fc_dma_unmap_single(ctrl->lport->dev, op->fcp_req.cmddma, + sizeof(op->cmd_iu), DMA_TO_DEVICE); out_on_error: return ret; } From df74eaad001cf669c332dc67ef91996532e6b52c Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 29 Jul 2026 18:59:58 +0800 Subject: [PATCH 51/81] nvme-pci: return error when parsing a quirk string fails quirks_param_set() reuses 'err', which param_set_copystring() left as 0, as the return value of the whole function. When nvme_parse_quirk_entry() fails to parse a field, the code jumps to out_free_qlist and returns that stale 0, so a malformed quirks= parameter is silently accepted as valid. Set err to -EINVAL before jumping out on a parse failure. Fixes: 7bb8c40f5ad8 ("nvme: add support for dynamic quirk configuration via module parameter") Reviewed-by: Christoph Hellwig Reviewed-by: Daniel Wagner Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 16d42e5138c8..375e7a1fc91d 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -213,6 +213,7 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) if (nvme_parse_quirk_entry(field, &qlist[i])) { pr_err("nvme: failed to parse quirk string %s\n", value); + err = -EINVAL; goto out_free_qlist; } From bf881dd20062db5e951a0d0703cb476df8c9fdee Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 29 Jul 2026 19:02:31 +0800 Subject: [PATCH 52/81] nvmet: reject out-of-range mdts values in configfs store nvmet_param_mdts_store() accepts any integer that kstrtoint() can parse and stores it directly into port->mdts. The value is only range-checked later, when the port is enabled: nvmet_enable_port() silently resets port->mdts to 0 if it is negative or greater than NVMET_MAX_MDTS. As a result, writing e.g. "mdts=1000" succeeds and reading the attribute back returns 1000, yet enabling the port quietly turns it into 0. This is confusing and hides the invalid input from the user. Validate the value against [0, NVMET_MAX_MDTS] in the store handler and reject anything out of range with -EINVAL, so the error is reported at write time and port->mdts never holds a value the port cannot use. Fixes: 0a5a94648627 ("nvmet: introduce new mdts configuration entry") Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 2b69ffcfc8df..413ee2d16d29 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -312,15 +312,17 @@ static ssize_t nvmet_param_mdts_store(struct config_item *item, const char *page, size_t count) { struct nvmet_port *port = to_nvmet_port(item); - int ret; + int ret, mdts; if (nvmet_is_port_enabled(port, __func__)) return -EACCES; - ret = kstrtoint(page, 0, &port->mdts); - if (ret) { - pr_err("Invalid value '%s' for mdts\n", page); + ret = kstrtoint(page, 0, &mdts); + if (ret || mdts < 0 || mdts > NVMET_MAX_MDTS) { + pr_err("Invalid value '%s' for mdts, should be 0-%d\n", + page, NVMET_MAX_MDTS); return -EINVAL; } + port->mdts = mdts; return count; } From 4a3f00262a044e8e15064b1a6860968bf0500bf4 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Thu, 9 Jul 2026 15:25:33 +0200 Subject: [PATCH 53/81] nvmet-tcp: bound SGL data length before allocating command buffers nvmet_tcp_map_data() reads the host-controlled 32-bit sgl->length and, for the in-capsule offset descriptor (type 0x01), checks it against port->inline_data_size before use. Any other SGL descriptor type -- including the non-inline transport SGL data-block descriptor (type (NVME_TRANSPORT_SGL_DATA_DESC << 4) | NVME_SGL_FMT_TRANSPORT_A, the type a real host uses for out-of-capsule writes) skips that check entirely and falls straight through to: cmd->req.sg = sgl_alloc(len, GFP_KERNEL, &cmd->req.sg_cnt); with len taken directly from the wire, unbounded up to 4 GiB. nvmet_req_init() only parses the command and never inspects sgl->length, and nvmet_check_transfer_len() -- the only other place transfer_len is validated -- runs later, from req->execute(), after the allocation has already happened. For a write command the target responds with an R2T and parks the command waiting for the host to send the data; if the host (or an unauthenticated peer that simply never follows up) never does, the sgl_alloc() buffer stays resident for the life of the command. NVMe/TCP has no mandatory authentication in the default configuration, so any peer able to reach the target portal and complete a Fabrics connect can drive this with a single crafted command, repeatable across queues and connections for amplification. This is unbounded kernel memory allocation triggered by a remote, effectively unauthenticated peer. Validate len against the same NVMET_TCP_MAXH2CDATA ceiling this file already uses to bound per-PDU H2C data, for every SGL descriptor type, before doing any allocation. This closes the gap for the non-inline descriptor while leaving the existing, tighter inline_data_size check in place for the in-capsule case. Runtime-verified on a v6.19 KASAN stand: with this bound in place, a crafted write command carrying an oversized non-inline SGL length is rejected before sgl_alloc() runs, where the same request previously drove an unbounded ~256 MiB kernel allocation (up to 4 GiB) that stayed resident pending an R2T the host never satisfies. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index cb6d37798d74..e4f603b2ace7 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -422,6 +422,19 @@ static int nvmet_tcp_map_data(struct nvmet_tcp_cmd *cmd) if (!len) return 0; + /* + * inline_data_size only bounds the in-capsule (type 0x01) SGL + * descriptor below. A non-inline transport SGL data-block + * descriptor skips that check entirely and would otherwise reach + * sgl_alloc() with an attacker-controlled len of up to 4 GiB, + * pinning that much kernel memory for a command that may never + * complete. Bound every descriptor type here, before allocating + * anything, using the same ceiling this file already applies to + * per-PDU H2C data. + */ + if (len > NVMET_TCP_MAXH2CDATA) + return NVME_SC_SGL_INVALID_DATA | NVME_STATUS_DNR; + if (sgl->type == ((NVME_SGL_FMT_DATA_DESC << 4) | NVME_SGL_FMT_OFFSET)) { if (!nvme_is_write(cmd->req.cmd)) From bc7f75eba50012ed447654d8ce169ebaba695193 Mon Sep 17 00:00:00 2001 From: Hari Mishal Date: Fri, 17 Jul 2026 16:43:53 +0200 Subject: [PATCH 54/81] nvmet: passthru: fix OOB reads when parsing ns id descriptor list nvmet_passthru_override_id_descs() walks a namespace identification descriptor list populated from the underlying passthru controller's Identify response, which is device reported. The loop advanced pos by device controlled amounts (sizeof(*cur) + nidl) without checking that the next descriptor header actually fits inside the buffer, so a malicious device could push pos to within a few bytes of the buffer end and cause cur->nidl, cur->nidt or the reserved field to be read past the allocation. Additionally, when a CSI descriptor lands exactly at the last valid header offset, cur + 1 points one byte past the end of the buffer. The unconditional memcpy(&csi, cur + 1, NVME_NIDT_CSI_LEN) could read that out-of-bounds byte and copy it back to the initiator via nvmet_copy_to_sgl(), leaking adjacent heap memory. Bounds check both the descriptor header and the CSI value before dereferencing them. Signed-off-by: Hari Mishal Signed-off-by: Keith Busch --- drivers/nvme/target/passthru.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/nvme/target/passthru.c b/drivers/nvme/target/passthru.c index e27f84e3cf2b..fa6527c537e2 100644 --- a/drivers/nvme/target/passthru.c +++ b/drivers/nvme/target/passthru.c @@ -53,13 +53,22 @@ static u16 nvmet_passthru_override_id_descs(struct nvmet_req *req) for (pos = 0; pos < NVME_IDENTIFY_DATA_SIZE; pos += len) { struct nvme_ns_id_desc *cur = data + pos; + if (pos + sizeof(*cur) > NVME_IDENTIFY_DATA_SIZE) + break; + if (cur->nidl == 0) break; + if (cur->nidt == NVME_NIDT_CSI) { + if (pos + sizeof(*cur) + NVME_NIDT_CSI_LEN > + NVME_IDENTIFY_DATA_SIZE) + break; + memcpy(&csi, cur + 1, NVME_NIDT_CSI_LEN); csi_seen = true; break; } + len = sizeof(struct nvme_ns_id_desc) + cur->nidl; } From 58202950e39c127d593ec4f0624d8b8985a285b1 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Sun, 26 Jul 2026 10:46:49 +0800 Subject: [PATCH 55/81] nvme-tcp: look up host_iface in the current netns nvme_tcp_alloc_ctrl() looks opts->host_iface up in &init_net, the boot-time netns. When called from any other netns - e.g. the selftest's ns2, where ns2eth1 actually lives - the lookup misses and the controller setup fails with "invalid interface passed": nvmet: adding nsid 1 to subsystem nqn.2014-08.org.nvmexpress.mptcpdev nvmet_tcp: enabling port 24660 (0.0.0.0:24099) # nvme discover -a 10.1.1.1 --tos=0x10 --host-iface=ns2eth1 nvme_tcp: invalid interface passed: ns2eth1 # failed to add controller, error invalid interface Look the device up in current->nsproxy->net_ns instead so the check sees the calling task's netns. Reviewed-by: Hannes Reinecke Signed-off-by: Geliang Tang Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 87d8067f3283..0b2ac150b675 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -2965,7 +2965,8 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev, } if (opts->mask & NVMF_OPT_HOST_IFACE) { - if (!__dev_get_by_name(&init_net, opts->host_iface)) { + if (!__dev_get_by_name(current->nsproxy->net_ns, + opts->host_iface)) { pr_err("invalid interface passed: %s\n", opts->host_iface); ret = -ENODEV; From ba98d6796d12258e837ece065d2ecb59d76ce4ff Mon Sep 17 00:00:00 2001 From: Jiang HongHui Date: Wed, 29 Jul 2026 19:02:06 +0800 Subject: [PATCH 56/81] nvmet-fc: fix invalid free in LS IOD error path nvmet_fc_alloc_ls_iodlist() advances iod while initializing the LS IOD array. If an rqstbuf allocation or response buffer DMA mapping fails, the unwind loop decrements iod past the start of the array. The final kfree(iod) therefore frees an address before the allocated object. This can be reproduced with nvme-fcloop and failslab by setting fail-nth to 6 before creating a target port. KASAN reports: BUG: KASAN: invalid-free in nvmet_fc_register_targetport Free of addr ffff88816cf8ff48 by task nvmet_fail_nth/9552 Free the original allocation base stored in tgtport->iod instead. With this fix applied, the same sysfs write with fail-nth=6 returns -ENOMEM without any KASAN report. Fixes: c53432030d86 ("nvme-fabrics: Add target support for FC transport") Cc: stable@vger.kernel.org Reviewed-by: Maurizio Lombardi Assisted-by: Codex:gpt-5 Signed-off-by: Jiang HongHui Signed-off-by: Keith Busch --- drivers/nvme/target/fc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/fc.c b/drivers/nvme/target/fc.c index d161707559ce..1b557775e033 100644 --- a/drivers/nvme/target/fc.c +++ b/drivers/nvme/target/fc.c @@ -566,7 +566,7 @@ nvmet_fc_alloc_ls_iodlist(struct nvmet_fc_tgtport *tgtport) list_del(&iod->ls_rcv_list); } - kfree(iod); + kfree(tgtport->iod); return -EFAULT; } From 0a96b9e440331bffbf049f80d7e5c96417d37e36 Mon Sep 17 00:00:00 2001 From: Zhengrong Li Date: Tue, 28 Jul 2026 16:26:01 +0800 Subject: [PATCH 57/81] nvmet: fix Reservation Register Replace for unregistered host with IEKEY When a host sends a Reservation Register command with RREGA=Replace and IEKEY=1 without being previously registered, nvmet returns Reservation Conflict. The NVMe specification states: "A host may replace its reservation key without regard to its registration status or current reservation key value by setting the Ignore Existing Key (IEKEY) bit to '1' in the Reservation Register command." Fix nvmet_pr_replace() to add a new registrant when the host is not found in the registrant list and IEKEY is set with a non-zero NRKEY. If IEKEY is set but NRKEY is zero, return Invalid Field since there is no valid reservation key to register. Tested with nvme-cli against nvmet-tcp: # no prior registration nvme resv-register /dev/nvmeXn1 -n 1 --rrega=2 --iekey --nrkey=0x9999 Before: RESERVATION_CONFLICT (0x4083) After: success, registrant created with rkey 0x9999 Fixes: 5a47c2080a73 ("nvmet: support reservation feature") Reviewed-by: Christoph Hellwig Reviewed-by: Guixin Liu Signed-off-by: Zhengrong Li Signed-off-by: Keith Busch --- drivers/nvme/target/pr.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index 5dd2f3553d8c..0948a690a1c0 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -355,9 +355,15 @@ static u16 nvmet_pr_replace(struct nvmet_req *req, u16 status = NVME_SC_RESERVATION_CONFLICT | NVME_STATUS_DNR; struct nvmet_ctrl *ctrl = req->sq->ctrl; struct nvmet_pr *pr = &req->ns->pr; - struct nvmet_pr_registrant *reg; + struct nvmet_pr_registrant *reg, *new = NULL; u64 nrkey = le64_to_cpu(d->nrkey); + if (ignore_key && nrkey) { + new = kzalloc_obj(*new); + if (!new) + return NVME_SC_INTERNAL; + } + down(&pr->pr_sem); list_for_each_entry_rcu(reg, &pr->registrant_list, entry) { if (uuid_equal(®->hostid, &ctrl->hostid)) { @@ -365,9 +371,26 @@ static u16 nvmet_pr_replace(struct nvmet_req *req, status = nvmet_pr_update_reg_attr(pr, reg, nvmet_pr_update_reg_rkey, &nrkey); - break; + goto free_data; } } + + if (ignore_key) { + if (!nrkey) { + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; + goto free_data; + } + INIT_LIST_HEAD(&new->entry); + new->rkey = nrkey; + uuid_copy(&new->hostid, &ctrl->hostid); + list_add_tail_rcu(&new->entry, &pr->registrant_list); + status = NVME_SC_SUCCESS; + goto out; + } + +free_data: + kfree(new); +out: up(&pr->pr_sem); return status; } From bededeaaeff404978a5a8e2a605a6c3017cddd3e Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Thu, 30 Jul 2026 20:36:24 +0900 Subject: [PATCH 58/81] nvme: zero the discard fallback page nvme_setup_discard() always maps sizeof(struct nvme_dsm_range) * NVME_DSM_MAX_RANGES = 4096 bytes as the DSM payload however many ranges the command declares, because some devices ignore the 'Number of Ranges' field - the Fixes: commit records two that read past the declared ranges. A single-range discard fills only the first 16 bytes. Normally the buffer comes from kzalloc() and the other 4080 bytes are zero. When that allocation fails the code falls back to the per-controller ctrl->discard_page, which nvme_init_ctrl() obtains with alloc_page(GFP_KERNEL) and nothing ever zeroes, so those 4080 bytes are whatever the page last held and are handed to the controller. Reaching it requires the kzalloc(GFP_ATOMIC | __GFP_NOWARN) to fail, that is memory pressure; it is not remotely triggerable. Failing the allocation under KMSAN reproduces it, with the leaked tail full of vmemmap struct page pointers. The extent in the report is a partial transfer of the payload, not the whole 4096 bytes; the 16-byte boundary in it is the one declared range: [ 11.991601] BUG: KMSAN: uninit-value in dma_map_phys+0x14c8/0x1900 [ 11.991969] dma_map_phys+0x14c8/0x1900 [ 11.992220] dma_map_page_attrs+0xcf/0x130 [ 11.992485] e1000_xmit_frame+0x4099/0x6d10 [ 11.992768] dev_hard_start_xmit+0x22f/0xa80 [ 11.993068] sch_direct_xmit+0x35c/0xcb0 [ 11.993315] __dev_queue_xmit+0x1ee5/0x5eb0 [ 11.993608] ip_finish_output2+0x1903/0x1c30 [ 11.993881] ip_finish_output+0x288/0x870 [ 11.994125] ip_output+0x15e/0x400 [ 11.994365] __ip_queue_xmit+0x1e85/0x1fb0 [ 11.994639] ip_queue_xmit+0x60/0x80 [ 11.994899] __tcp_transmit_skb+0x4e71/0x5fa0 [ 11.995210] tcp_write_xmit+0x3a36/0x9160 [ 11.995533] __tcp_push_pending_frames+0xc5/0x3c0 [ 11.995854] tcp_push+0x7dc/0x840 [ 11.996076] tcp_sendmsg_locked+0x766c/0x8400 [ 11.996371] tcp_sendmsg+0x4b/0x90 [ 11.996572] inet_sendmsg+0x134/0x2a0 [ 11.996823] __sock_sendmsg+0x265/0x360 [ 11.997076] sock_sendmsg+0x100/0x1e0 [ 11.997293] nvme_tcp_try_send+0x196f/0x6370 [ 11.997605] nvme_tcp_queue_rq+0x1d54/0x20b0 [ 11.997882] blk_mq_dispatch_rq_list+0x5ee/0x2e50 [ 11.998175] __blk_mq_sched_dispatch_requests+0x16dc/0x24a0 [ 11.998539] blk_mq_sched_dispatch_requests+0x11b/0x2c0 [ 11.998865] blk_mq_run_work_fn+0x13b/0x280 [ 11.999146] process_scheduled_works+0x966/0x1ad0 [ 11.999465] worker_thread+0xe44/0x1480 [ 11.999709] kthread+0x53b/0x600 [ 11.999927] ret_from_fork+0x29f/0x7c0 [ 12.000191] ret_from_fork_asm+0x1a/0x30 [ 12.000460] [ 12.000558] Uninit was created at: [ 12.000788] __alloc_frozen_pages_noprof+0x8bf/0xd30 [ 12.001096] alloc_pages_mpol+0x1d0/0x5f0 [ 12.001326] alloc_pages_noprof+0x102/0x290 [ 12.001627] nvme_init_ctrl+0x5a3/0x9f0 [ 12.001891] nvme_tcp_create_ctrl+0xd75/0x19b0 [ 12.002170] nvmf_dev_write+0x4c68/0x4fd0 [ 12.002426] vfs_write+0x587/0x1a10 [ 12.002636] __x64_sys_write+0x207/0x4f0 [ 12.002874] x64_sys_call+0x2ff0/0x3ea0 [ 12.003123] do_syscall_64+0x147/0x3b0 [ 12.003400] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 12.003680] [ 12.003777] Bytes 16-2843 of 2844 are uninitialized [ 12.004068] Memory access of size 2844 starts at ffff888109f82000 [ 12.004412] [ 12.004530] CPU: 0 UID: 0 PID: 101 Comm: kworker/0:1H Not tainted 7.2.0-rc5-NVMECTL-gf5098b6bae76 #1 PREEMPT(lazy) [ 12.005127] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 12.005762] Workqueue: kblockd blk_mq_run_work_fn [ 12.006073] ===================================================== Allocate the page with __GFP_ZERO. The single allocation site covers every use of it: bytes no discard has written stay zero, and bytes one did write hold that controller's own range list, which it has already been sent. Fixes: 530436c45ef2 ("nvme: Discard workaround for non-conformant devices") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index cb93ada4376a..975181a74fae 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -5223,7 +5223,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, BUILD_BUG_ON(NVME_DSM_MAX_RANGES * sizeof(struct nvme_dsm_range) > PAGE_SIZE); - ctrl->discard_page = alloc_page(GFP_KERNEL); + ctrl->discard_page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!ctrl->discard_page) { ret = -ENOMEM; goto out; From 79aba4c9403419d822972d2851f2a96a2c0531cf Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 10:18:57 +0800 Subject: [PATCH 59/81] nvmet: fix NULL pointer dereference in nvmet_execute_identify_nslist() When a host issues an Identify command with CNS 07h (Active Namespace ID List for a specific I/O Command Set), nvmet_execute_identify_nslist() is called with match_css set. The command-set filter dereferences req->ns, but this handler never calls nvmet_req_find_ns(), so req->ns is always NULL (nvmet_req_init() resets it to NULL). As soon as an enabled namespace with an NSID greater than the requested value exists, req->ns->csi dereferences a NULL pointer and oopses. Besides the crash, the comparison is logically wrong: to filter the list by command set it must test the command set of the namespace being iterated, not a single fixed value. Use the loop variable ns->csi. Fixes: 61c9967cd634 ("nvmet: implement active command set ns list") Signed-off-by: Guixin Liu Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/target/admin-cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c index 0b24d31f966d..3fde09b4d78a 100644 --- a/drivers/nvme/target/admin-cmd.c +++ b/drivers/nvme/target/admin-cmd.c @@ -960,7 +960,7 @@ static void nvmet_execute_identify_nslist(struct nvmet_req *req, bool match_css) nvmet_for_each_enabled_ns(&ctrl->subsys->namespaces, idx, ns) { if (ns->nsid <= min_nsid) continue; - if (match_css && req->ns->csi != req->cmd->identify.csi) + if (match_css && ns->csi != req->cmd->identify.csi) continue; list[i++] = cpu_to_le32(ns->nsid); if (i == buf_size / sizeof(__le32)) From 751709592d2626eaa8dc17ef8137796758a3c37f Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 10:18:58 +0800 Subject: [PATCH 60/81] nvmet: propagate percpu_ref_init() failure in nvmet_ns_enable() The return value of percpu_ref_init() is discarded. At this point ret is 0 from the preceding successful steps, so when the allocation inside percpu_ref_init() fails the code jumps to the out_pr_exit cleanup chain which ends with "return ret", i.e. reports success. The configfs enable store then tells userspace the namespace was enabled even though it was not and its backing device has already been torn down. Capture the return value so the failure is propagated. Fixes: 408232680707 ("nvmet: Fix crash when a namespace is disabled") Signed-off-by: Guixin Liu Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index a2403a808360..30a1eb77f60b 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -610,7 +610,8 @@ int nvmet_ns_enable(struct nvmet_ns *ns) goto out_dev_put; } - if (percpu_ref_init(&ns->ref, nvmet_destroy_namespace, 0, GFP_KERNEL)) + ret = percpu_ref_init(&ns->ref, nvmet_destroy_namespace, 0, GFP_KERNEL); + if (ret) goto out_pr_exit; nvmet_ns_changed(subsys, ns->nsid); From cb144c2f67128abfa5c7ba33318617d19f192156 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 10:18:59 +0800 Subject: [PATCH 61/81] nvme-pci: release descriptor pools on probe failure The per-NUMA-node descriptor DMA pools are created lazily from nvme_init_hctx_common() once the admin tag set is allocated, but they are only destroyed in nvme_remove() via nvme_release_descriptor_pools(). Any probe failure after the admin tag set has been allocated unwinds through the out_disable label and nvme_pci_free_ctrl(), neither of which releases the pools, leaking the dma_pool objects. Release the descriptor pools in the out_disable error path. It must not be added to nvme_pci_free_ctrl(), as that would double-free against nvme_remove() on the normal teardown path. Fixes: d977506f8863 ("nvme-pci: make PRP list DMA pools per-NUMA-node") Signed-off-by: Guixin Liu Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Kanchan Joshi Reviewed-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 375e7a1fc91d..ef06627b21ee 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -3854,6 +3854,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) nvme_dev_remove_admin(dev); nvme_dbbuf_dma_free(dev); nvme_free_queues(dev, 0); + nvme_release_descriptor_pools(dev); out_release_iod_mempool: mempool_destroy(dev->dmavec_mempool); out_dev_unmap: From 53cdaeab2e30e0cb849a74b94f93729ad98946b1 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 10:19:01 +0800 Subject: [PATCH 62/81] nvme: raise FDP placement handle cap to U8_MAX and warn on overflow The RUH status buffer and the placement-handle clamp used S8_MAX - 1 (126) as the maximum descriptor count. That value was picked only so the io-mgmt-receive result fit in a page, not because of any protocol or driver restriction. The meaningful upper bound is U8_MAX: write hints (bio->bi_write_stream) are u8, so placement handles beyond U8_MAX can never be selected. Size the buffer and clamp nr_plids to U8_MAX. Suggested-by: Kanchan Joshi Signed-off-by: Guixin Liu Reviewed-by: Kanchan Joshi Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 975181a74fae..a59abd770aff 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -33,6 +33,13 @@ #define NVME_MINORS (1U << MINORBITS) +/* + * Write hints (bio->bi_write_stream) are u8, so FDP placement handles beyond + * U8_MAX can never be selected. Cap the handle count to bound both the RUH + * status buffer and the per-head plids array. + */ +#define NVME_MAX_PLIDS U8_MAX + struct nvme_ns_info { struct nvme_ns_ids ids; u32 nsid; @@ -2353,7 +2360,7 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) if (!info->runs) return ret; - size = struct_size(ruhs, ruhsd, S8_MAX - 1); + size = struct_size(ruhs, ruhsd, NVME_MAX_PLIDS); ruhs = kzalloc(size, GFP_KERNEL); if (!ruhs) return -ENOMEM; @@ -2368,7 +2375,7 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) goto free; } - head->nr_plids = min(le16_to_cpu(ruhs->nruhsd), S8_MAX - 1); + head->nr_plids = min(le16_to_cpu(ruhs->nruhsd), NVME_MAX_PLIDS); if (!head->nr_plids) goto free; From 5bb96cc218835769ab74ec7f3ea2bf81fbffe955 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 11:38:00 +0800 Subject: [PATCH 63/81] nvmet: fix heap out-of-bounds read in nvmet_auth_negotiate() nvmet_execute_auth_send() allocates the DH-HMAC-CHAP message buffer with the host-supplied transfer length (tl) and hands it to nvmet_auth_negotiate() without passing tl along. nvmet_auth_negotiate() then reads the negotiate header and, for each of the halen hash identifiers and dhlen DH group identifiers, indexes into the fixed idlist[60] array (hashes at idlist[0..halen), groups at idlist[30..]). Neither the transfer length nor halen/dhlen is validated. A malicious or non-conformant host can report a tl smaller than the negotiate structure, or a halen/dhlen larger than the array (both are u8, up to 255), making the loops read past the end of the allocated buffer (heap out-of-bounds read). The sibling nvmet_auth_reply() already validates tl against the structure size; the negotiate path did not. Pass tl into nvmet_auth_negotiate(), reject a tl that does not cover the negotiate data plus one full protocol descriptor, and reject halen/dhlen larger than NVME_AUTH_DHCHAP_MAX_DH_IDS. Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index d1b39e64d877..92f8a76f10ff 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -31,12 +31,16 @@ void nvmet_auth_sq_init(struct nvmet_sq *sq) sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; } -static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d) +static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d, u32 tl) { 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; + if (tl < sizeof(*data) + + sizeof(struct nvmf_auth_dhchap_protocol_descriptor)) + return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; + 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, @@ -72,6 +76,10 @@ static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d) NVME_AUTH_DHCHAP_AUTH_ID) return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; + if (data->auth_protocol[0].dhchap.dhlen > NVME_AUTH_DHCHAP_MAX_DH_IDS || + data->auth_protocol[0].dhchap.halen > NVME_AUTH_DHCHAP_MAX_HASH_IDS) + 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]; @@ -317,7 +325,7 @@ void nvmet_execute_auth_send(struct nvmet_req *req) } else if (data->auth_id != req->sq->dhchap_step) goto done_failure1; /* Validate negotiation parameters */ - dhchap_status = nvmet_auth_negotiate(req, d); + dhchap_status = nvmet_auth_negotiate(req, d, tl); if (dhchap_status == 0) req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE; From 87d5b9864c8118d26f54de4b66d2bddf2c659272 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:33 +0200 Subject: [PATCH 64/81] nvme-apple: Destroy the admin queue on removal The admin queue is allocated with blk_mq_alloc_queue() but never destroyed. nvme_free_ctrl() only drops the last reference and blk_mq_exit_queue() and blk_sync_queue() never run: the hctx is never moved to q->unused_hctx_list and the timeout timer and work stay armed on a queue that is about to be freed which will eventually oops inside blk_mq_timeout_work(). This can only be triggered when the controller fails to come up and is then immediately torn down again which is why no one ever ran into this before. Let's just copy what the pcie driver does: unquiesce and destroy the admin queue before nvme_uninit_ctrl(). With this the following WARN followed by a panic no longer happens: WARNING: block/blk-mq.c:4390 at blk_mq_release+0x194/0x238, CPU#4: kworker/u34:4/119 CPU: 4 UID: 0 PID: 119 Comm: kworker/u34:4 Not tainted 7.2.0-rc1-dirty #248 PREEMPT Hardware name: Apple Mac mini (M1, 2020) (DT) Workqueue: nvme-wq apple_nvme_remove_dead_ctrl_work pstate: 61400005 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) pc : blk_mq_release+0x194/0x238 lr : blk_mq_release+0x58/0x238 sp : ffffc000833a3b50 x29: ffffc000833a3b50 x28: ffff80001d0450f8 x27: ffff800020c95200 x26: 0000000000000088 x25: 0000000000000000 x24: ffff800020f36805 x23: 0000000000000000 x22: ffffc00081a86878 x21: ffff800020be9c60 x20: 0000000000000000 x19: ffff800022501698 x18: 000000000000000a x17: 7365757165722066 x16: 666f7265776f7020 x15: 0000000000000000 x14: 0000000000000028 x13: 0000000000004def x12: 0000000000000003 x11: 0000000000000000 x10: 0000000000000000 x9 : ffffc000805b4fc8 x8 : ffffc00081915820 x7 : ffffc00081c4f3c8 x6 : 0000000000000001 x5 : 0000000000000004 x4 : ffff800022498d80 x3 : ffffc000833a3b14 x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff800022501698 Call trace: blk_mq_release+0x194/0x238 (P) blk_put_queue+0x8c/0xf0 nvme_free_ctrl+0x4c/0x260 device_release+0x44/0x128 kobject_put+0xa0/0x120 put_device+0x1c/0x40 nvme_uninit_ctrl+0x48/0x60 apple_nvme_remove+0x54/0xb0 platform_remove+0x28/0x40 device_remove+0x54/0x98 device_release_driver_internal+ device_release_driver+0x20/0x38 apple_nvme_remove_dead_ctrl_wor process_one_work+0x1f4/0x770 worker_thread+0x1b8/0x360 kthread+0x140/0x160 ret_from_fork+0x10/0x20 irq event stamp: 448 hardirqs last enabled at (447):in_unlock_irqrestore+0x74/0x80 hardirqs last disabled at (448): [] el1_brk64+0x20/0x60 softirqs last enabled at (0): [ess+0xb28/0x2698 softirqs last disabled at (0): [<0000000000000000>] 0x0 ---[ end trace 0000000000000000 Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 Mem abort info: ESR = 0x0000000096000005 EC = 0x25: DABT (current EL), SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x05: level 1 translation fault Data abort info: ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 CM = 0, WnR = 0, TnD = 0, TagA GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [0000000000000000] user address Internal error: Oops: 0000000096000005 [#1] SMP CPU: 7 UID: 0 PID: 54 Comm: kwor 7.2.0-rc1-dirty #248PREEMPT Tainted: [W]=WARN Hardware name: Apple Mac mini (M1, 2020) (DT) Workqueue: kblockd blk_mq_timeou pstate: 01400005 (nzcv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) pc : percpu_ref_tryget_many.cons lr : percpu_ref_tryget_many.constprop.0+0xc0/0x168 sp : ffffc000829cbce0 x29: ffffc000829cbce0 x28: ffff800020be9f48 x27: ffff800013e503c0 x26: 0000000000000108 x25: 000009c05 x23: 0000000000000000 x22: ffffc000819f5000 x21: ffff800020be9f48 x20: ffff8001deda4808 x19: ffff8000a x17: 00000000580e1fac x16: ffffc00082bbbb7c x15: 0000000000000000 x14: 0000000000000028 x13: 000000001 x11: 0000000000000000 x10: 0000000000000000 x9 : ffffc000829cbc20 x8 : ffffc00081915820 x7 : ffffc0001 x5 : ffff80001ca77d08 x4 : 0000000000000000 x3 : ffff80001ca77cb8 x2 : 0000000000000000 x1 : 000000007 Call trace: percpu_ref_tryget_many.constpro blk_mq_timeout_work+0x48/0x298 process_one_work+0x1f4/0x770 worker_thread+0x1b8/0x360 kthread+0x140/0x160 ret_from_fork+0x10/0x20 Code: 91282000 97ed44b2 17ffffd2 ---[ end trace 0000000000000000 ]--- Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/apple.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 09eb2295ceee..7e6a83b30731 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -1650,6 +1650,15 @@ static void apple_nvme_remove(struct platform_device *pdev) nvme_stop_ctrl(&anv->ctrl); nvme_remove_namespaces(&anv->ctrl); apple_nvme_disable(anv, true); + if (anv->ctrl.admin_q && !blk_queue_dying(anv->ctrl.admin_q)) { + /* + * If the controller was reset during removal, it's possible + * user requests may be waiting on a stopped queue. Start the + * queue to flush these to completion. + */ + nvme_unquiesce_admin_queue(&anv->ctrl); + blk_mq_destroy_queue(anv->ctrl.admin_q); + } nvme_uninit_ctrl(&anv->ctrl); if (apple_rtkit_is_running(anv->rtk)) { From 94dd5804938d6681dbf26f023b1356d511f4fc48 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:34 +0200 Subject: [PATCH 65/81] nvme-apple: Don't set a DMA direction for commands without a data transfer Setting the DMA direction for commands that don't do any transfer likely triggered the PRP NULL check for which we needed a chicken bit. That bit has disappeared starting with macOS 15 so let's just do this correctly instead. Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/apple.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 7e6a83b30731..0eb31ab196cf 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -341,7 +341,9 @@ static void apple_nvme_submit_cmd_t8103(struct apple_nvme_queue *q, tcb->length = cmd->rw.length; tcb->command_id = tag; - if (nvme_is_write(cmd)) + if (!cmd->common.dptr.prp1) + tcb->dma_flags = 0; + else if (nvme_is_write(cmd)) tcb->dma_flags = APPLE_ANS_TCB_DMA_TO_DEVICE; else tcb->dma_flags = APPLE_ANS_TCB_DMA_FROM_DEVICE; From cc0fec9b42cfbc69d70cb4c4b616408a7037b445 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:35 +0200 Subject: [PATCH 66/81] nvme-apple: Never set the opcode in the NVMMU TCB macOS always sets this to zero and the firmware starting with macOS 15 has started to complain about what we're doing here. Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/apple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 0eb31ab196cf..321d7f5ab902 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -335,7 +335,7 @@ static void apple_nvme_submit_cmd_t8103(struct apple_nvme_queue *q, u32 tag = nvme_tag_from_cid(cmd->common.command_id); struct apple_nvmmu_tcb *tcb = &q->tcbs[tag]; - tcb->opcode = cmd->common.opcode; + tcb->opcode = 0; tcb->prp1 = cmd->common.dptr.prp1; tcb->prp2 = cmd->common.dptr.prp2; tcb->length = cmd->rw.length; From 69d22a6b2f6984200d92dac689f8b00cc3d7d736 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:36 +0200 Subject: [PATCH 67/81] nvme: Add a quirk for page aligned admin queue buffers Apple controllers seem to require any queue buffers on the admin queue to be aligned to the NVMe controller page size. Weirdly, this constraint does not apply to the i/o queue where any alignment is fine. This has always been required on pre-M1 controllers and is required starting with macOS 15 firmware or post-M4 controllers again. On M1/M2/M3 we only got away with this because there was a chicken bit to disable this requirement. Let's add a quirk that enforces this alignment. Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/core.c | 5 ++++- drivers/nvme/host/nvme.h | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index a59abd770aff..1322c678f4eb 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2089,7 +2089,10 @@ static void nvme_set_ctrl_limits(struct nvme_ctrl *ctrl, lim->max_integrity_segments = ctrl->max_integrity_segments; lim->virt_boundary_mask = ctrl->ops->get_virt_boundary(ctrl, is_admin); lim->max_segment_size = UINT_MAX; - lim->dma_alignment = 3; + if (is_admin && (ctrl->quirks & NVME_QUIRK_ADMIN_PAGE_ALIGN)) + lim->dma_alignment = NVME_CTRL_PAGE_SIZE - 1; + else + lim->dma_alignment = 3; } static bool nvme_update_disk_info(struct nvme_ns *ns, struct nvme_id_ns *id, diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 862464301d01..28cec87e4427 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -178,6 +178,11 @@ enum nvme_quirks { * Align dma pool segment size to 512 bytes */ NVME_QUIRK_DMAPOOL_ALIGN_512 = (1 << 22), + + /* + * Admin queue DMA buffers must be page aligned + */ + NVME_QUIRK_ADMIN_PAGE_ALIGN = (1 << 23), }; static inline char *nvme_quirk_name(enum nvme_quirks q) @@ -229,6 +234,8 @@ static inline char *nvme_quirk_name(enum nvme_quirks q) return "broken_msi"; case NVME_QUIRK_DMAPOOL_ALIGN_512: return "dmapool_align_512"; + case NVME_QUIRK_ADMIN_PAGE_ALIGN: + return "admin_page_align"; } return "unknown"; From ea2160c7b78187ea9ab08c3190eef237c4ee99a7 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:37 +0200 Subject: [PATCH 68/81] nvme-apple: Require page aligned buffers on the admin queue Now that we have a quick to align buffers on the admin queue to the NVMe controller page size use it for Apple controllers. This fixes pre-M1 controllers, which always rejected unaligned requests, and also makes this driver work for M4 SoCs and for M1/M2/M3 SoCs that have been updated to the firmware shipped with macOS 15. Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/apple.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 321d7f5ab902..806dd55d5518 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -1597,7 +1597,8 @@ static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev) } ret = nvme_init_ctrl(&anv->ctrl, anv->dev, &nvme_ctrl_ops, - NVME_QUIRK_SKIP_CID_GEN | NVME_QUIRK_IDENTIFY_CNS); + NVME_QUIRK_SKIP_CID_GEN | NVME_QUIRK_IDENTIFY_CNS | + NVME_QUIRK_ADMIN_PAGE_ALIGN); if (ret) { dev_err_probe(dev, ret, "Failed to initialize nvme_ctrl"); goto put_dev; From 8ce883fd068b7ba9ab493cd3ecca3a7ea868c375 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 6 Aug 2026 17:27:38 +0200 Subject: [PATCH 69/81] nvme-apple: Drop the PRP null check chicken bit Now that we program the DMA direction correctly the NULL check that used to make commands fail passes. Another side effect of this bit was that non-align buffers on the admin queue were silently allowed and that's been fixed now as well and we this don't need this chicken bit anymore. More importantly, starting with the firmware installed with macOS 15, which is required for M4 but can also be installed on the previous SoCs, the controller no longer exposes this control register and any access SErrors instead. Just drop the write entirely. Fixes: 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver") Tested-by: Joshua Peisach Tested-by: Janne Grunau Tested-by: Nick Chan Signed-off-by: Sven Peter --- drivers/nvme/host/apple.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c index 806dd55d5518..c63e28c75766 100644 --- a/drivers/nvme/host/apple.c +++ b/drivers/nvme/host/apple.c @@ -47,9 +47,6 @@ #define APPLE_ANS_BOOT_STATUS 0x1300 #define APPLE_ANS_BOOT_STATUS_OK 0xde71ce55 -#define APPLE_ANS_UNKNOWN_CTRL 0x24008 -#define APPLE_ANS_PRP_NULL_CHECK BIT(11) - #define APPLE_ANS_LINEAR_SQ_CTRL 0x24908 #define APPLE_ANS_LINEAR_SQ_EN BIT(0) @@ -1143,17 +1140,6 @@ static void apple_nvme_reset_work(struct work_struct *work) /* Setup the NVMMU for the maximum admin and IO queue depth */ writel(anv->hw->max_queue_depth - 1, anv->mmio_nvme + APPLE_NVMMU_NUM_TCBS); - - /* - * This is probably a chicken bit: without it all commands - * where any PRP is set to zero (including those that don't use - * that field) fail and the co-processor complains about - * "completed with err BAD_CMD-" or a "NULL_PRP_PTR_ERR" in the - * syslog - */ - writel(readl(anv->mmio_nvme + APPLE_ANS_UNKNOWN_CTRL) & - ~APPLE_ANS_PRP_NULL_CHECK, - anv->mmio_nvme + APPLE_ANS_UNKNOWN_CTRL); } /* Setup the admin queue */ From 659ae9d02cb5d72c76f74fff7441eb8fb64d8f5c Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 4 Aug 2026 21:36:25 +0000 Subject: [PATCH 70/81] nvmet: pci-epf: put CQ ref on create_cq mapping failure nvmet_pci_epf_create_cq() calls nvmet_cq_create(), which takes a reference on the controller and installs the completion queue. If the subsequent PCI address-space mapping fails or returns a too-small partial mapping, the function jumps to err_internal / err_unmap_queue without calling nvmet_cq_put(). The matching put in nvmet_pci_epf_delete_cq() is gated on NVMET_PCI_EPF_Q_LIVE, which is only set after the mapping succeeds, so teardown never releases these references. A remote PCI host that drives Create IO CQ commands with a failing PRP1/pci_addr therefore leaks the CQ and a controller reference on each attempt. Drop the CQ reference on the mapping-failure paths. The err_internal and err_unmap_queue labels are only reachable after nvmet_cq_create() has succeeded, so this pairs the create/put correctly. Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Signed-off-by: Keith Busch --- drivers/nvme/target/pci-epf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 4e9db96ebfec..794e88d1d9a5 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -1339,6 +1339,7 @@ static u16 nvmet_pci_epf_create_cq(struct nvmet_ctrl *tctrl, nvmet_pci_epf_mem_unmap(ctrl->nvme_epf, &cq->pci_map); err_internal: status = NVME_SC_INTERNAL | NVME_STATUS_DNR; + nvmet_cq_put(&cq->nvme_cq); err: if (test_and_clear_bit(NVMET_PCI_EPF_Q_IRQ_ENABLED, &cq->flags)) nvmet_pci_epf_remove_irq_vector(ctrl, cq->vector); From c9e9bb757971485b4e8414b1744507af186d72c9 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 30 Jul 2026 15:18:39 +0900 Subject: [PATCH 71/81] nvmet: pci-epf: fix use-after-free in nvmet_pci_epf_exec_iod_work() nvmet_pci_epf_exec_iod_work() submits an I/O command with req->execute() and then waits for the command to complete and transfers the data back to the host. This wait is not needed for commands that do not transfer data from the device to the host. To decide whether that wait is needed, it reads iod->data_len and iod->dma_dir after calling req->execute(). However, once req->execute() is called, the command may complete asynchronously on another CPU. For commands that do not require a device-to-host data transfer, nvmet_pci_epf_queue_response() calls nvmet_pci_epf_complete_iod() directly, which can free the iod before it reads iod->data_len and iod->dma_dir, resulting in the KFENCE use-after- free: BUG: KFENCE: use-after-free read in nvmet_pci_epf_exec_iod_work+0x288/0x798 [nvmet_pci_epf] Use-after-free read at 0x00000000fdfa6d03 (in kfence-#63): nvmet_pci_epf_exec_iod_work+0x288/0x798 [nvmet_pci_epf] process_one_work+0x15c/0x4f0 worker_thread+0x18c/0x30c kthread+0x130/0x140 ret_from_fork+0x10/0x20 kfence-#63: 0x00000000e3de0e71-0x00000000c938ad62, size=712, cache=kmalloc-1k allocated by task 10 on cpu 0 at 73.995480s (0.005122s ago): mempool_kmalloc+0x1c/0x28 mempool_alloc_noprof+0x40/0x9c nvmet_pci_epf_poll_sqs_work+0xd4/0x344 [nvmet_pci_epf] process_one_work+0x15c/0x4f0 worker_thread+0x18c/0x30c kthread+0x130/0x140 ret_from_fork+0x10/0x20 freed by task 131 on cpu 3 at 73.995521s (0.008385s ago): mempool_kfree+0x10/0x20 mempool_free+0x44/0x64 nvmet_pci_epf_free_iod+0x88/0x98 [nvmet_pci_epf] nvmet_pci_epf_cq_work+0xfc/0x280 [nvmet_pci_epf] process_one_work+0x15c/0x4f0 worker_thread+0x18c/0x30c kthread+0x130/0x140 ret_from_fork+0x10/0x20 Fix this by referring to iod->data_len and iod->dma_dir before calling req->execute(). The remaining iod accesses such as iod->status are only reached on the device-to-host read path. In this case, nvmet_pci_epf_queue_response() signals iod->done instead of freeing the iod, so the iod stays valid. Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Keith Busch --- drivers/nvme/target/pci-epf.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 794e88d1d9a5..346a4badd6b2 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -1595,6 +1595,7 @@ static void nvmet_pci_epf_exec_iod_work(struct work_struct *work) struct nvmet_pci_epf_iod *iod = container_of(work, struct nvmet_pci_epf_iod, work); struct nvmet_req *req = &iod->req; + bool no_wait; int ret; if (!iod->ctrl->link_up) { @@ -1639,14 +1640,16 @@ static void nvmet_pci_epf_exec_iod_work(struct work_struct *work) } } - req->execute(req); - /* * If we do not have data to transfer after the command execution * finishes, nvmet_pci_epf_queue_response() will complete the command * directly. No need to wait for the completion in this case. */ - if (!iod->data_len || iod->dma_dir != DMA_TO_DEVICE) + no_wait = !iod->data_len || iod->dma_dir != DMA_TO_DEVICE; + + req->execute(req); + + if (no_wait) return; wait_for_completion(&iod->done); From f594863967d87b7fcbff6e724d51135fd701a13d Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Tue, 4 Aug 2026 11:36:05 +0800 Subject: [PATCH 72/81] nvmet: fix NULL pointer dereference in nvmet_execute_identify_ns_zns() When a host issues an Identify command with CNS 05h (I/O Command Set specific Identify Namespace) and CSI 02h (ZNS) targeting a file-backed namespace, nvmet_execute_identify_ns_zns() calls bdev_is_zoned() on req->ns->bdev. A file-backed namespace has no block device, so req->ns->bdev is NULL and bdev_is_zoned() dereferences it, oopsing. The I/O command set is selected by the host-supplied CSI field and the command is routed here whenever CONFIG_BLK_DEV_ZONED is enabled, independent of the namespace backing type, so any file-backed namespace is exposed. Reject the command with Invalid Field when the namespace is not backed by a block device. Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support") Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/zns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c index f00921931eb6..a13befd5f3ad 100644 --- a/drivers/nvme/target/zns.c +++ b/drivers/nvme/target/zns.c @@ -116,7 +116,7 @@ void nvmet_execute_identify_ns_zns(struct nvmet_req *req) mutex_unlock(&req->ns->subsys->lock); } - if (!bdev_is_zoned(req->ns->bdev)) { + if (!req->ns->bdev || !bdev_is_zoned(req->ns->bdev)) { status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; req->error_loc = offsetof(struct nvme_identify, nsid); goto out; From 1161be71d1ecf7dc785382114c71afed0349531e Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Wed, 15 Jul 2026 11:57:52 -0400 Subject: [PATCH 73/81] nvme: reject passthrough of driver-managed Set Features Since commit b58da2d270db ("nvme: update keep alive interval when kato is modified"), a Set Features (KATO) passthrough command lets userspace start keep-alive on any transport. nvme_keep_alive_work() allocates with BLK_MQ_REQ_RESERVED, but nvme_alloc_admin_tag_set() reserves admin tags only for fabrics, so on other transports the allocation trips WARN_ON_ONCE() in blk_mq_get_tag() and fails: nvme nvme0: keep-alive failed: -11 Several Set Features change controller state the driver manages itself and cannot react to when set behind its back. Reject these in nvme_admin_cmd_allowed(): - KATO on non-fabrics (keep-alive is only armed for fabrics; on PCIe it has no reserved tag and harms idle power states) - Host Behavior Support, Host Memory Buffer, Number of Queues, and Autonomous Power State Transition (all driver-managed) Keep Alive on fabrics is unchanged; I/O commands are unaffected as the check is confined to the admin path (ns == NULL). Link: https://lore.kernel.org/linux-nvme/20260523225629.3964037-1-coshi036@gmail.com/ Fixes: b58da2d270db ("nvme: update keep alive interval when kato is modified") Found by FuzzNvme. Acked-by: Sungwoo Kim Acked-by: Dave Tian Acked-by: Weidong Zhu Signed-off-by: Chao Shi Signed-off-by: Keith Busch --- drivers/nvme/host/ioctl.c | 111 ++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 39 deletions(-) diff --git a/drivers/nvme/host/ioctl.c b/drivers/nvme/host/ioctl.c index f4ea52d11945..6539d4750098 100644 --- a/drivers/nvme/host/ioctl.c +++ b/drivers/nvme/host/ioctl.c @@ -14,45 +14,54 @@ enum { NVME_IOCTL_PARTITION = (1 << 1), }; -static bool nvme_cmd_allowed(struct nvme_ns *ns, struct nvme_command *c, - unsigned int flags, bool open_for_write) +static bool nvme_admin_cmd_allowed(struct nvme_ctrl *ctrl, + struct nvme_command *c) { - u32 effects; - - /* - * Do not allow unprivileged passthrough on partitions, as that allows an - * escape from the containment of the partition. - */ - if (flags & NVME_IOCTL_PARTITION) - goto admin; - - /* - * Do not allow unprivileged processes to send vendor specific or fabrics - * commands as we can't be sure about their effects. - */ - if (c->common.opcode >= nvme_cmd_vendor_start || - c->common.opcode == nvme_fabrics_command) - goto admin; - /* * Do not allow unprivileged passthrough of admin commands except * for a subset of identify commands that contain information required * to form proper I/O commands in userspace and do not expose any * potentially sensitive information. */ - if (!ns) { - if (c->common.opcode == nvme_admin_identify) { - switch (c->identify.cns) { - case NVME_ID_CNS_NS: - case NVME_ID_CNS_CS_NS: - case NVME_ID_CNS_NS_CS_INDEP: - case NVME_ID_CNS_CS_CTRL: - case NVME_ID_CNS_CTRL: - return true; - } + switch (c->common.opcode) { + case nvme_admin_identify: + switch (c->identify.cns) { + case NVME_ID_CNS_NS: + case NVME_ID_CNS_CS_NS: + case NVME_ID_CNS_NS_CS_INDEP: + case NVME_ID_CNS_CS_CTRL: + case NVME_ID_CNS_CTRL: + return true; } - goto admin; + break; + case nvme_admin_set_features: + /* + * Reject Set Features that change controller state the driver + * manages itself; setting them behind the driver's back from + * userspace leaves it unable to react correctly. Keep Alive is + * only armed for fabrics - on other transports it has no + * reserved tag and harms idle power states. + */ + switch (le32_to_cpu(c->features.fid) & 0xff) { + case NVME_FEAT_KATO: + if (ctrl->ops->flags & NVME_F_FABRICS) + break; + fallthrough; + case NVME_FEAT_HOST_BEHAVIOR: + case NVME_FEAT_HOST_MEM_BUF: + case NVME_FEAT_NUM_QUEUES: + case NVME_FEAT_AUTO_PST: + return false; + } + break; } + return capable(CAP_SYS_ADMIN); +} + +static bool nvme_ns_cmd_allowed(struct nvme_ns *ns, struct nvme_command *c, + bool open_for_write) +{ + u32 effects; /* * Check if the controller provides a Commands Supported and Effects log @@ -61,7 +70,7 @@ static bool nvme_cmd_allowed(struct nvme_ns *ns, struct nvme_command *c, */ effects = nvme_command_effects(ns->ctrl, ns, c->common.opcode); if (!(effects & NVME_CMD_EFFECTS_CSUPP)) - goto admin; + return capable(CAP_SYS_ADMIN); /* * Don't allow passthrough for command that have intrusive (or unknown) @@ -70,7 +79,7 @@ static bool nvme_cmd_allowed(struct nvme_ns *ns, struct nvme_command *c, if (effects & ~(NVME_CMD_EFFECTS_CSUPP | NVME_CMD_EFFECTS_LBCC | NVME_CMD_EFFECTS_UUID_SEL | NVME_CMD_EFFECTS_SCOPE_MASK)) - goto admin; + return capable(CAP_SYS_ADMIN); /* * Only allow I/O commands that transfer data to the controller or that @@ -79,11 +88,34 @@ static bool nvme_cmd_allowed(struct nvme_ns *ns, struct nvme_command *c, */ if ((nvme_is_write(c) || (effects & NVME_CMD_EFFECTS_LBCC)) && !open_for_write) - goto admin; + return capable(CAP_SYS_ADMIN); return true; -admin: - return capable(CAP_SYS_ADMIN); +} + +static bool nvme_cmd_allowed(struct nvme_ctrl *ctrl, struct nvme_ns *ns, + struct nvme_command *c, unsigned int flags, + bool open_for_write) +{ + /* + * Do not allow unprivileged passthrough on partitions, as that + * allows an escape from the containment of the partition. + */ + if (flags & NVME_IOCTL_PARTITION) + return capable(CAP_SYS_ADMIN); + + /* + * Do not allow unprivileged processes to send vendor specific or + * fabrics commands as we can't be sure about their effects. + */ + if (c->common.opcode >= nvme_cmd_vendor_start || + c->common.opcode == nvme_fabrics_command) + return capable(CAP_SYS_ADMIN); + + if (!ns) + return nvme_admin_cmd_allowed(ctrl, c); + + return nvme_ns_cmd_allowed(ns, c, open_for_write); } /* @@ -261,7 +293,7 @@ static int nvme_submit_io(struct nvme_ns *ns, struct nvme_user_io __user *uio, c.rw.lbat = cpu_to_le16(io.apptag); c.rw.lbatm = cpu_to_le16(io.appmask); - if (!nvme_cmd_allowed(ns, &c, flags, open_for_write)) + if (!nvme_cmd_allowed(ns->ctrl, ns, &c, flags, open_for_write)) return -EACCES; return nvme_submit_user_cmd(ns->queue, &c, io.addr, length, metadata, @@ -311,7 +343,7 @@ static int nvme_user_cmd(struct nvme_ctrl *ctrl, struct nvme_ns *ns, c.common.cdw14 = cpu_to_le32(cmd.cdw14); c.common.cdw15 = cpu_to_le32(cmd.cdw15); - if (!nvme_cmd_allowed(ns, &c, 0, open_for_write)) + if (!nvme_cmd_allowed(ctrl, ns, &c, 0, open_for_write)) return -EACCES; if (cmd.timeout_ms) @@ -358,7 +390,7 @@ static int nvme_user_cmd64(struct nvme_ctrl *ctrl, struct nvme_ns *ns, c.common.cdw14 = cpu_to_le32(cmd.cdw14); c.common.cdw15 = cpu_to_le32(cmd.cdw15); - if (!nvme_cmd_allowed(ns, &c, flags, open_for_write)) + if (!nvme_cmd_allowed(ctrl, ns, &c, flags, open_for_write)) return -EACCES; if (cmd.timeout_ms) @@ -453,6 +485,7 @@ static int nvme_uring_cmd_io(struct nvme_ctrl *ctrl, struct nvme_ns *ns, const struct nvme_uring_cmd *cmd = io_uring_sqe128_cmd(ioucmd->sqe, struct nvme_uring_cmd); struct request_queue *q = ns ? ns->queue : ctrl->admin_q; + bool open_for_write = ioucmd->file->f_mode & FMODE_WRITE; struct nvme_uring_data d; struct nvme_command c; struct iov_iter iter; @@ -483,7 +516,7 @@ static int nvme_uring_cmd_io(struct nvme_ctrl *ctrl, struct nvme_ns *ns, c.common.cdw14 = cpu_to_le32(READ_ONCE(cmd->cdw14)); c.common.cdw15 = cpu_to_le32(READ_ONCE(cmd->cdw15)); - if (!nvme_cmd_allowed(ns, &c, 0, ioucmd->file->f_mode & FMODE_WRITE)) + if (!nvme_cmd_allowed(ctrl, ns, &c, 0, open_for_write)) return -EACCES; d.metadata = READ_ONCE(cmd->metadata); From 36ac05f7cfd59d90c597071304b14e98090d5dd1 Mon Sep 17 00:00:00 2001 From: Dmitry Bogdanov Date: Thu, 16 Jul 2026 16:42:19 +0200 Subject: [PATCH 74/81] nvme-tcp: fix usage of page_frag_cache nvme uses page_frag_cache to preallocate PDU for each preallocated request of block device. Block devices are created in parallel threads, consequently page_frag_cache is used in not thread-safe manner. That leads to incorrect refcounting of backstore pages and premature free. That can be catched by !sendpage_ok inside network stack: WARNING: CPU: 7 PID: 467 at ../net/core/skbuff.c:6931 skb_splice_from_iter+0xfa/0x310. tcp_sendmsg_locked+0x782/0xce0 tcp_sendmsg+0x27/0x40 sock_sendmsg+0x8b/0xa0 nvme_tcp_try_send_cmd_pdu+0x149/0x2a0 Then random panic may occur. Fix that by serializing the usage of page_frag_cache. Fixes: 4e893ca81170 ("nvme_core: scan namespaces asynchronously") Signed-off-by: Dmitry Bogdanov Signed-off-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 0b2ac150b675..1d303e54e13e 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -108,6 +108,7 @@ struct nvme_tcp_queue { struct mutex queue_lock; struct mutex send_mutex; + struct mutex pf_cache_lock; struct llist_head req_list; struct list_head send_list; @@ -552,9 +553,11 @@ static int nvme_tcp_init_request(struct blk_mq_tag_set *set, struct nvme_tcp_queue *queue = &ctrl->queues[queue_idx]; u8 hdgst = nvme_tcp_hdgst_len(queue); + mutex_lock(&queue->pf_cache_lock); req->pdu = page_frag_alloc(&queue->pf_cache, sizeof(struct nvme_tcp_cmd_pdu) + hdgst, GFP_KERNEL | __GFP_ZERO); + mutex_unlock(&queue->pf_cache_lock); if (!req->pdu) return -ENOMEM; @@ -1419,9 +1422,11 @@ static int nvme_tcp_alloc_async_req(struct nvme_tcp_ctrl *ctrl) struct nvme_tcp_request *async = &ctrl->async_req; u8 hdgst = nvme_tcp_hdgst_len(queue); + mutex_lock(&queue->pf_cache_lock); async->pdu = page_frag_alloc(&queue->pf_cache, sizeof(struct nvme_tcp_cmd_pdu) + hdgst, GFP_KERNEL | __GFP_ZERO); + mutex_unlock(&queue->pf_cache_lock); if (!async->pdu) return -ENOMEM; @@ -1463,6 +1468,7 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) kfree(queue->pdu); mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); + mutex_destroy(&queue->pf_cache_lock); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&queue->nvme_tcp_sk_key); @@ -1790,6 +1796,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, INIT_LIST_HEAD(&queue->send_list); mutex_init(&queue->send_mutex); INIT_WORK(&queue->io_work, nvme_tcp_io_work); + mutex_init(&queue->pf_cache_lock); if (qid > 0) queue->cmnd_capsule_len = nctrl->ioccsz * 16; @@ -1930,6 +1937,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); + mutex_destroy(&queue->pf_cache_lock); return ret; } From 86985da12699360a2b20748c5a492ddd92db8c47 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Mon, 13 Jul 2026 18:00:00 +0800 Subject: [PATCH 75/81] nvmet: zns: reject full zone report when buffer is too small Zone Management Receive uses the Partial Report (PR) bit in dword 13. On a partial report (PR bit set), the host accepts an incomplete listing and Number of Zones must not exceed the zone descriptors copied to the host buffer. On a full report (PR bit clear), Number of Zones is the total number of matching zones and every descriptor must fit in the buffer (ZNS Command Set Specification Rev 1.2, section 3.4.2). nvmet_bdev_zone_zmgmt_recv_work() already caps Number of Zones for partial reports, but on a full report it may still succeed when the buffer only holds part of the matching descriptors. Reject the command in that case. Signed-off-by: Xixin Liu Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/zns.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/zns.c b/drivers/nvme/target/zns.c index a13befd5f3ad..23a17c02abee 100644 --- a/drivers/nvme/target/zns.c +++ b/drivers/nvme/target/zns.c @@ -295,11 +295,18 @@ static void nvmet_bdev_zone_zmgmt_recv_work(struct work_struct *w) } /* - * When partial bit is set nr_zones must indicate the number of zone - * descriptors actually transferred. + * Partial report (PR bit set): the host accepts an incomplete listing, + * so cap Number of Zones to the descriptors that fit in the buffer. + * Full report (PR bit clear): Number of Zones is the match count; fail + * if the buffer cannot hold every matching zone descriptor. */ - if (req->cmd->zmr.pr) + if (req->cmd->zmr.pr) { rz_data.nr_zones = min(rz_data.nr_zones, rz_data.out_nr_zones); + } else if (rz_data.nr_zones > rz_data.out_nr_zones) { + req->error_loc = offsetof(struct nvme_zone_mgmt_recv_cmd, numd); + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; + goto out; + } nr_zones = cpu_to_le64(rz_data.nr_zones); status = nvmet_copy_to_sgl(req, 0, &nr_zones, sizeof(nr_zones)); From 7fa3f73f6c8ddc5f0425b50fb2a626a782ef7d12 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sat, 1 Aug 2026 17:18:17 +0900 Subject: [PATCH 76/81] nvme-tcp: reject a read that transferred too few bytes nvme_tcp_recv_data() completes a request once the current C2HData PDU has been consumed. Nothing compares the total bytes received against the length the command asked for: struct nvme_tcp_request has no receive-side counter, queue->data_remaining is per queue, and blk_mq_end_request() completes for blk_rq_bytes(rq) unconditionally with no residual concept anywhere above. A controller can therefore answer a 4096-byte read with 512 bytes and have it reported as a complete read; user space then gets 4096 bytes of which 3584 are whatever was already in the page. I reproduced that with a test target. Count the bytes received and refuse to complete a successful read whose count does not match, at the two NVME_TCP_F_DATA_SUCCESS paths and in nvme_tcp_process_nvme_cqe(). The success test shifts req->status right by one, because the driver keeps the wire value there and shifts it on completion, so the check must see what the completion path will see. Only REQ_OP_READ is checked, because there the length comes from the sectors the request covers; a passthrough command is built by its submitter, which picks both command and buffer, so the kernel has nothing to compare against. Fixes: 3f2304f8c6d6 ("nvme-tcp: add NVMe over TCP host driver") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 1d303e54e13e..3655f7607be0 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -80,6 +80,7 @@ struct nvme_tcp_request { struct bio *curr_bio; struct iov_iter iter; + u32 data_recvd; /* send state */ size_t offset; @@ -617,6 +618,29 @@ static void nvme_tcp_error_recovery(struct nvme_ctrl *ctrl) queue_work(nvme_reset_wq, &to_tcp_ctrl(ctrl)->err_work); } +/* + * NVMe has no short read: a read that completes successfully must + * have transferred everything it asked for. + */ +static bool nvme_tcp_data_in_short(struct nvme_tcp_queue *queue, + struct request *rq) +{ + struct nvme_tcp_request *req = blk_mq_rq_to_pdu(rq); + + if (le16_to_cpu(req->status) >> 1) + return false; + if (req_op(rq) != REQ_OP_READ || !req->data_len) + return false; + if (likely(req->data_recvd == req->data_len)) + return false; + + dev_err(queue->ctrl->ctrl.device, + "queue %d tag %#x short data-in: got %u of %u\n", + nvme_tcp_queue_id(queue), rq->tag, + req->data_recvd, req->data_len); + return true; +} + static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue, struct nvme_completion *cqe) { @@ -636,6 +660,9 @@ static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue, if (req->status == cpu_to_le16(NVME_SC_SUCCESS)) req->status = cqe->status; + if (unlikely(nvme_tcp_data_in_short(queue, rq))) + return -EPROTO; + if (!nvme_try_complete_req(rq, req->status, cqe->result)) nvme_complete_rq(rq); queue->nr_cqe++; @@ -958,6 +985,7 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb, *len -= recv_len; *offset += recv_len; queue->data_remaining -= recv_len; + req->data_recvd += recv_len; } if (!queue->data_remaining) { @@ -966,6 +994,8 @@ static int nvme_tcp_recv_data(struct nvme_tcp_queue *queue, struct sk_buff *skb, queue->ddgst_remaining = NVME_TCP_DIGEST_LENGTH; } else { if (pdu->hdr.flags & NVME_TCP_F_DATA_SUCCESS) { + if (unlikely(nvme_tcp_data_in_short(queue, rq))) + return -EPROTO; nvme_tcp_end_request(rq, le16_to_cpu(req->status)); queue->nr_cqe++; @@ -1014,6 +1044,9 @@ static int nvme_tcp_recv_ddgst(struct nvme_tcp_queue *queue, pdu->command_id); struct nvme_tcp_request *req = blk_mq_rq_to_pdu(rq); + if (unlikely(nvme_tcp_data_in_short(queue, rq))) + return -EPROTO; + nvme_tcp_end_request(rq, le16_to_cpu(req->status)); queue->nr_cqe++; } @@ -2746,6 +2779,7 @@ static blk_status_t nvme_tcp_setup_cmd_pdu(struct nvme_ns *ns, req->status = cpu_to_le16(NVME_SC_SUCCESS); req->offset = 0; req->data_sent = 0; + req->data_recvd = 0; req->pdu_len = 0; req->pdu_sent = 0; req->h2cdata_left = 0; From 3a4aa9e6ad3e35f8e24d5eaf38ee4d437075fb36 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sat, 1 Aug 2026 17:18:18 +0900 Subject: [PATCH 77/81] nvme-tcp: do not accept C2HData based on blk_rq_payload_bytes() alone Commit 25e5cb780e62 ("nvme-tcp: fix possible crash in write_zeroes processing") established that blk_rq_payload_bytes() must not be read without first checking blk_rq_nr_phys_segments(), and recorded the result in nvme_tcp_setup_cmd_pdu() as req->data_len. The receive side was left as it was. The two differ for REQ_OP_WRITE_ZEROES, which has no physical segments but a non-zero blk_rq_bytes(), so setup leaves req->iter untouched while the receive gate lets a C2HData through and nvme_tcp_recv_data() copies into whatever the previous command on that tag left there. The driver-private area is zeroed only when the tag set is allocated. Reproduced with a test target that leaves a residual iterator on a tag and then sends a C2HData for a WRITE_ZEROES command on the same tag: BUG: KASAN: wild-memory-access in _copy_to_iter+0x642/0x1330 Write of size 512 at addr ffe728c2175dfa81 by task kworker/0:1H/103 CPU: 0 UID: 0 PID: 103 Comm: kworker/0:1H Not tainted 7.2.0-rc5-NVMETCP-gf5098b6bae76 #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: nvme_tcp_wq nvme_tcp_io_work Call Trace: dump_stack_lvl+0x53/0x70 kasan_report+0xce/0x100 ? _copy_to_iter+0x642/0x1330 kasan_check_range+0x105/0x1b0 __asan_memcpy+0x3c/0x60 _copy_to_iter+0x642/0x1330 ? __pfx_sock_has_perm+0x10/0x10 ? worker_thread+0x45b/0xd10 ? __pfx__copy_to_iter+0x10/0x10 ? _raw_spin_lock_bh+0x83/0xe0 ? __pfx__raw_spin_lock_bh+0x10/0x10 __skb_datagram_iter+0xf3/0x820 ? __pfx_simple_copy_to_iter+0x10/0x10 ? __asan_memcpy+0x3c/0x60 ? skb_copy_bits+0x58d/0x830 skb_copy_datagram_iter+0x37/0x120 nvme_tcp_recv_skb+0xa07/0x4320 ? __pfx_nvme_tcp_recv_skb+0x10/0x10 __tcp_read_sock+0x1ab/0x810 ? __pfx_nvme_tcp_recv_skb+0x10/0x10 ? __pfx_lock_sock_nested+0x10/0x10 ? __pfx___tcp_read_sock+0x10/0x10 nvme_tcp_try_recv+0x152/0x1e0 ? __pfx_nvme_tcp_try_recv+0x10/0x10 ? __pfx_mutex_unlock+0x10/0x10 nvme_tcp_io_work+0x1e4/0x6c0 ? __schedule+0x181a/0x49f0 ? __pfx_nvme_tcp_io_work+0x10/0x10 process_one_work+0x633/0x1030 Keep the blk_rq_payload_bytes() test and add req->data_len to it. The old test is what rejects a C2HData naming a tag that is no longer in flight, because blk_update_request() zeroes rq->__data_len on completion; req->data_len and req->curr_bio are driver-private and survive completion, so they cannot stand in for it. Setup initialises the iterator only when both req->curr_bio and req->data_len are set, so the gate now tests the same two. Fixes: 25e5cb780e62 ("nvme-tcp: fix possible crash in write_zeroes processing") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 3655f7607be0..46c2acc6abe0 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -673,6 +673,7 @@ static int nvme_tcp_process_nvme_cqe(struct nvme_tcp_queue *queue, static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue, struct nvme_tcp_data_pdu *pdu) { + struct nvme_tcp_request *req; struct request *rq; rq = nvme_find_rq(nvme_tcp_tagset(queue), pdu->command_id); @@ -683,7 +684,8 @@ static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue, return -ENOENT; } - if (!blk_rq_payload_bytes(rq)) { + req = blk_mq_rq_to_pdu(rq); + if (!blk_rq_payload_bytes(rq) || !req->curr_bio || !req->data_len) { dev_err(queue->ctrl->ctrl.device, "queue %d tag %#x unexpected data\n", nvme_tcp_queue_id(queue), rq->tag); From 6efbc52237facda35d2d874fe1765bb4839275d8 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 29 Jul 2026 14:46:02 +0900 Subject: [PATCH 78/81] nvme-tcp: fix host memory disclosure on R2T for a read command nvme_tcp_handle_r2t() does not check the direction of the request the R2T refers to. A malicious controller can send an R2T for a READ and the host will answer it: nvme_tcp_setup_h2c_data_pdu() builds the H2CData header and nvme_tcp_try_send_data() sends the request's data buffer. That buffer is the READ destination, so its contents go to the controller. The command then completes normally and nothing is logged. Against a test controller that answers every READ with an R2T, a 4096 byte buffered read returned all 4096 bytes, split over two R2Ts. The pages contained stale kernel data, including an array of struct page pointers. Reject an R2T for a request that is not a write. Fixes: 3f2304f8c6d6 ("nvme-tcp: add NVMe over TCP host driver") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 46c2acc6abe0..62c5e38f5207 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -779,6 +779,13 @@ static int nvme_tcp_handle_r2t(struct nvme_tcp_queue *queue, } req = blk_mq_rq_to_pdu(rq); + if (unlikely(rq_data_dir(rq) != WRITE)) { + dev_err(queue->ctrl->ctrl.device, + "req %d unexpected r2t for a non-write command\n", + rq->tag); + return -EPROTO; + } + if (unlikely(!r2t_length)) { dev_err(queue->ctrl->ctrl.device, "req %d r2t len is %u, probably a bug...\n", From c05c86681170e381e24498257db3a879deb1ae89 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Mon, 10 Aug 2026 18:02:58 -0400 Subject: [PATCH 79/81] nvme: ratelimit the completion-path messages driven by device data nvme_find_rq() and nvme_handle_cqe() print an unratelimited message for every completion queue entry whose command id does not resolve to an in-flight request. Both are reached from the completion interrupt path (nvme_irq() -> nvme_poll_cq() -> nvme_handle_cqe()) and the decision to print is made entirely from device-supplied data, so a controller that posts a stream of bogus command ids drives unbounded printk from hard interrupt context. This is not hypothetical. A single boot under an emulated controller that posts invalid completions produced 846 "could not locate request for tag 0x0", 846 "invalid id 0 completed on queue 2" and 123 "genctr mismatch" lines. Once the tag set has been torn down every subsequent completion resolves to nothing, so the print rate is bounded only by how fast the device can post entries. Ratelimit the three messages. The information they carry is diagnostic and repeats, so the suppression count printed by the ratelimit helpers is enough to tell that the condition persists. This matches how the other device-driven error prints in the driver are already handled, for example the status messages in nvme_log_error() and nvme_log_err_passthru(). nvme_find_rq() lives in nvme.h and is shared by pci, tcp, rdma, apple and target-loop, so all transports are covered. Found by FuzzNvme. Signed-off-by: Chao Shi Signed-off-by: Keith Busch --- drivers/nvme/host/nvme.h | 6 +++--- drivers/nvme/host/pci.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 28cec87e4427..75e5d5a8a77c 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -692,12 +692,12 @@ static inline struct request *nvme_find_rq(struct blk_mq_tags *tags, rq = blk_mq_tag_to_rq(tags, tag); if (unlikely(!rq)) { - pr_err("could not locate request for tag %#x\n", - tag); + pr_err_ratelimited("could not locate request for tag %#x\n", + tag); return NULL; } if (unlikely(nvme_genctr_mask(nvme_req(rq)->genctr) != genctr)) { - dev_err(nvme_req(rq)->ctrl->device, + dev_err_ratelimited(nvme_req(rq)->ctrl->device, "request %#x genctr mismatch (got %#x expected %#x)\n", tag, genctr, nvme_genctr_mask(nvme_req(rq)->genctr)); return NULL; diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index ef06627b21ee..c19b9c2ea89a 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -1586,9 +1586,9 @@ static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, req = nvme_find_rq(nvme_queue_tagset(nvmeq), command_id); if (unlikely(!req)) { - dev_warn(nvmeq->dev->ctrl.device, - "invalid id %d completed on queue %d\n", - command_id, le16_to_cpu(cqe->sq_id)); + dev_warn_ratelimited(nvmeq->dev->ctrl.device, + "invalid id %d completed on queue %d\n", + command_id, le16_to_cpu(cqe->sq_id)); return; } From 22eb631bf86ee3246f47885e4fa94154a46863e4 Mon Sep 17 00:00:00 2001 From: "Ewan D. Milne" Date: Wed, 13 May 2026 15:25:51 -0400 Subject: [PATCH 80/81] nvme: nvme-fc: Fix nvme_fc_create_hw_io_queues() queue deletion in error path nvme_fc_create_hw_io_queues() will call __nvme_fc_delete_hw_queue() for the last queue on which __nvme_fc_create_hw_queue() reported an error when deleting all the io queues if they cannot all be created. This is incorrect since the last queue did not actually get created. The most recent change to this code was commit 17a1ec08ce70 ("nvme/fc: simplify error handling of nvme_fc_create_hw_io_queues") which moved the cleanup to the delete_queues: label and changed the loop bounds, however the code was not correct prior to this change in a different way. The original commit e399441de911 ("nvme-fabrics: Add host support for FC transport") had a different error which called __nvme_fc_delete_hw_queue() on queue index 0 which is used for the admin queue. Fix this by correcting the initial loop index when deleting the io queues. Fixes: 17a1ec08ce70 ("nvme/fc: simplify error handling of nvme_fc_create_hw_io_queues") Fixes: e399441de911 ("nvme-fabrics: Add host support for FC transport") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Maurizio Lombardi Reviewed-by: Laurence Oberman Reviewed-by: Justin Tee Signed-off-by: Ewan D. Milne Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 40f9da2833ff..023710e08e0d 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2324,7 +2324,7 @@ nvme_fc_create_hw_io_queues(struct nvme_fc_ctrl *ctrl, u16 qsize) return 0; delete_queues: - for (; i > 0; i--) + for (--i; i > 0; i--) __nvme_fc_delete_hw_queue(ctrl, &ctrl->queues[i], i); return ret; } From f1a8846e06388113dfdbb89dee005083fa9afdf9 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Thu, 13 Aug 2026 15:18:50 +0200 Subject: [PATCH 81/81] nvmet: fix max_qid race between configfs and controller allocation The function nvmet_subsys_attr_qid_max_store() can race against nvmet_alloc_ctrl() when a subsystem's max_qid limit is modified. Suppose max_qid is currently 64. If nvmet_alloc_ctrl() executes: ctrl->sqs = kzalloc_objs(struct nvmet_sq *, subsys->max_qid + 1); and at this exact point, a userspace process changes max_qid to 128, nvmet_subsys_attr_qid_max_store() will set the new max_qid value. It attempts to delete active controllers to force a reconnect, but the new controller won't be deleted because it hasn't been added to the subsys->ctrls list yet. nvmet_alloc_ctrl() then proceeds and adds the new controller to the subsys->ctrls list. Later, when nvmet_install_queue() is called, it will see max_qid set to 128, but the memory allocated for sqs is only sized for 64 entries. This results in a KASAN out-of-bounds warning and potential memory corruptions. Fix this by protecting the queue allocations and list insertion in nvmet_alloc_ctrl() with down_read(&nvmet_config_sem). Because nvmet_subsys_attr_qid_max_store() acquires down_write(&nvmet_config_sem) to modify the attribute, this safely prevents the configfs writer from modifying max_qid during controller creation. Copy the max_qid from the subsystem to the controller's structure during the allocation; ctrl->max_qid never changes as long as the controller remains in LIVE state, so this will prevent similar race conditions. Fixes: 3e980f5995e0 ("nvmet: expose max queues to configfs") Reported-by: syzbot+2626e846cd2585c9aa67@syzkaller.appspotmail.com Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/target/admin-cmd.c | 8 ++--- drivers/nvme/target/core.c | 51 +++++++++++++++++-------------- drivers/nvme/target/fabrics-cmd.c | 2 +- drivers/nvme/target/nvmet.h | 6 ++++ drivers/nvme/target/pci-epf.c | 2 +- 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c index 3fde09b4d78a..7764a3c0195c 100644 --- a/drivers/nvme/target/admin-cmd.c +++ b/drivers/nvme/target/admin-cmd.c @@ -1337,7 +1337,7 @@ static u16 nvmet_set_feat_arbitration(struct nvmet_req *req) void nvmet_execute_set_features(struct nvmet_req *req) { - struct nvmet_subsys *subsys = nvmet_req_subsys(req); + struct nvmet_ctrl *ctrl = nvmet_req_ctrl(req); u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); u32 cdw11 = le32_to_cpu(req->cmd->common.cdw11); u16 status = 0; @@ -1359,7 +1359,7 @@ void nvmet_execute_set_features(struct nvmet_req *req) break; } nvmet_set_result(req, - (subsys->max_qid - 1) | ((subsys->max_qid - 1) << 16)); + (ctrl->max_qid - 1) | ((ctrl->max_qid - 1) << 16)); break; case NVME_FEAT_IRQ_COALESCE: status = nvmet_set_feat_irq_coalesce(req); @@ -1496,7 +1496,7 @@ void nvmet_get_feat_async_event(struct nvmet_req *req) void nvmet_execute_get_features(struct nvmet_req *req) { - struct nvmet_subsys *subsys = nvmet_req_subsys(req); + struct nvmet_ctrl *ctrl = nvmet_req_ctrl(req); u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); u16 status = 0; @@ -1536,7 +1536,7 @@ void nvmet_execute_get_features(struct nvmet_req *req) break; case NVME_FEAT_NUM_QUEUES: nvmet_set_result(req, - (subsys->max_qid-1) | ((subsys->max_qid-1) << 16)); + (ctrl->max_qid-1) | ((ctrl->max_qid-1) << 16)); break; case NVME_FEAT_KATO: nvmet_get_feat_kato(req); diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 30a1eb77f60b..d74c01c98f19 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -878,7 +878,7 @@ u16 nvmet_check_cqid(struct nvmet_ctrl *ctrl, u16 cqid, bool create) if (!ctrl->cqs) return NVME_SC_INTERNAL | NVME_STATUS_DNR; - if (cqid > ctrl->subsys->max_qid) + if (cqid > ctrl->max_qid) return NVME_SC_QID_INVALID | NVME_STATUS_DNR; if ((create && ctrl->cqs[cqid]) || (!create && !ctrl->cqs[cqid])) @@ -926,7 +926,7 @@ u16 nvmet_check_sqid(struct nvmet_ctrl *ctrl, u16 sqid, if (!ctrl->sqs) return NVME_SC_INTERNAL | NVME_STATUS_DNR; - if (sqid > ctrl->subsys->max_qid) + if (sqid > ctrl->max_qid) return NVME_SC_QID_INVALID | NVME_STATUS_DNR; if ((create && ctrl->sqs[sqid]) || @@ -1655,23 +1655,6 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) if (!ctrl->changed_ns_list) goto out_free_ctrl; - ctrl->sqs = kzalloc_objs(struct nvmet_sq *, subsys->max_qid + 1); - if (!ctrl->sqs) - goto out_free_changed_ns_list; - - ctrl->cqs = kzalloc_objs(struct nvmet_cq *, subsys->max_qid + 1); - if (!ctrl->cqs) - goto out_free_sqs; - - ret = ida_alloc_range(&cntlid_ida, - subsys->cntlid_min, subsys->cntlid_max, - GFP_KERNEL); - if (ret < 0) { - args->status = NVME_SC_CONNECT_CTRL_BUSY | NVME_STATUS_DNR; - goto out_free_cqs; - } - ctrl->cntlid = ret; - /* * Discovery controllers may use some arbitrary high value * in order to cleanup stale discovery sessions @@ -1685,9 +1668,28 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) ctrl->err_counter = 0; spin_lock_init(&ctrl->error_lock); - nvmet_start_keep_alive_timer(ctrl); - + down_read(&nvmet_config_sem); mutex_lock(&subsys->lock); + + ctrl->max_qid = subsys->max_qid; + + ctrl->sqs = kzalloc_objs(struct nvmet_sq *, ctrl->max_qid + 1); + if (!ctrl->sqs) + goto out_free_changed_ns_list; + + ctrl->cqs = kzalloc_objs(struct nvmet_cq *, ctrl->max_qid + 1); + if (!ctrl->cqs) + goto out_free_sqs; + + ret = ida_alloc_range(&cntlid_ida, + subsys->cntlid_min, subsys->cntlid_max, + GFP_KERNEL); + if (ret < 0) { + args->status = NVME_SC_CONNECT_CTRL_BUSY | NVME_STATUS_DNR; + goto out_free_cqs; + } + ctrl->cntlid = ret; + ret = nvmet_ctrl_init_pr(ctrl); if (ret) goto init_pr_fail; @@ -1695,6 +1697,9 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) nvmet_setup_p2p_ns_map(ctrl, args->p2p_client); nvmet_debugfs_ctrl_setup(ctrl); mutex_unlock(&subsys->lock); + up_read(&nvmet_config_sem); + + nvmet_start_keep_alive_timer(ctrl); if (args->hostid) uuid_copy(&ctrl->hostid, args->hostid); @@ -1724,14 +1729,14 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) return ctrl; init_pr_fail: - mutex_unlock(&subsys->lock); - nvmet_stop_keep_alive_timer(ctrl); ida_free(&cntlid_ida, ctrl->cntlid); out_free_cqs: kfree(ctrl->cqs); out_free_sqs: kfree(ctrl->sqs); out_free_changed_ns_list: + mutex_unlock(&subsys->lock); + up_read(&nvmet_config_sem); kfree(ctrl->changed_ns_list); out_free_ctrl: kfree(ctrl); diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c index 7cadd1c9e44c..42d1d1811671 100644 --- a/drivers/nvme/target/fabrics-cmd.c +++ b/drivers/nvme/target/fabrics-cmd.c @@ -370,7 +370,7 @@ static void nvmet_execute_io_connect(struct nvmet_req *req) goto out; } - if (unlikely(qid > ctrl->subsys->max_qid)) { + if (unlikely(qid > ctrl->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); diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index c672c9bf3053..e362d7913a38 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -268,6 +268,7 @@ struct nvmet_ctrl { uuid_t hostid; u16 cntlid; + u16 max_qid; u32 kato; struct nvmet_port *port; @@ -756,6 +757,11 @@ static inline struct nvmet_subsys *nvmet_req_subsys(struct nvmet_req *req) return req->sq->ctrl->subsys; } +static inline struct nvmet_ctrl *nvmet_req_ctrl(struct nvmet_req *req) +{ + return req->sq->ctrl; +} + static inline bool nvmet_is_disc_subsys(struct nvmet_subsys *subsys) { return subsys->type != NVME_NQN_NVME; diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 346a4badd6b2..803e85df50e5 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -2081,7 +2081,7 @@ static int nvmet_pci_epf_create_ctrl(struct nvmet_pci_epf *nvme_epf, } /* Allocate our queues, up to the maximum number. */ - ctrl->nr_queues = min(ctrl->tctrl->subsys->max_qid + 1, max_nr_queues); + ctrl->nr_queues = min(ctrl->tctrl->max_qid + 1, max_nr_queues); ret = nvmet_pci_epf_alloc_queues(ctrl); if (ret) goto out_put_ctrl;