* [PATCH 6.12.y 2/4] nvme: refactor the atomic write unit detection
2026-09-11 0:15 ` [PATCH 6.12.y 1/4] nvme: all namespaces in a subsystem must adhere to a common atomic write size Sasha Levin
@ 2026-09-11 0:15 ` Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 3/4] nvme: fix atomic write size validation Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 4/4] nvme: skip the zoned limits update if the zone info query failed Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-09-11 0:15 UTC (permalink / raw)
To: stable; +Cc: Christoph Hellwig, Luis Chamberlain, John Garry, Sasha Levin
From: Christoph Hellwig <hch@lst.de>
[ Upstream commit b2e607fecac15e07f50269c080e2e71b5049dfa2 ]
Move all the code out of nvme_update_disk_info into the helper, and
rename the helper to have a somewhat less clumsy name.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Reviewed-by: John Garry <john.g.garry@oracle.com>
Stable adaptation for 6.12:
- Keep the existing atomic write helper refactor, but omit
BLK_FEAT_ATOMIC_WRITES: this tree enables atomic writes through the
hardware limits and does not define that feature flag.
- Call the existing discard setup helper from nvme_update_disk_info()
after setting logical_block_size. Save the disk-info validity result
and defer clearing capacity until after atomic-size validation. This
preserves the result while leaving the zoned update insertion point
clear for 3838e80fcfb32e62baffb63c6dc0a60153665a4d.
- Adapt the preceding dependency's atomic-size error path to the
single-argument blk_mq_unfreeze_queue() API and cancel the pending
limits update before unfreezing, releasing limits_lock on rejection.
No additional functions are introduced.
Stable-dep-of: 3838e80fcfb3 ("nvme: skip the zoned limits update if the zone info query failed")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/nvme/host/core.c | 84 ++++++++++++++++++++++------------------
1 file changed, 46 insertions(+), 38 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 3f2586086f6ec..deffad78d1dcf 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -1995,20 +1995,50 @@ static void nvme_configure_metadata(struct nvme_ctrl *ctrl,
}
-static void nvme_update_atomic_write_disk_info(struct nvme_ns *ns,
- struct nvme_id_ns *id, struct queue_limits *lim,
- u32 bs, u32 atomic_bs)
+static u32 nvme_configure_atomic_write(struct nvme_ns *ns,
+ struct nvme_id_ns *id, struct queue_limits *lim, u32 bs)
{
- unsigned int boundary = 0;
+ u32 atomic_bs, boundary = 0;
- if (id->nsfeat & NVME_NS_FEAT_ATOMICS && id->nawupf) {
- if (le16_to_cpu(id->nabspf))
+ /*
+ * We do not support an offset for the atomic boundaries.
+ */
+ if (id->nabo)
+ return bs;
+
+ if ((id->nsfeat & NVME_NS_FEAT_ATOMICS) && id->nawupf) {
+ /*
+ * Use the per-namespace atomic write unit when available.
+ */
+ atomic_bs = (1 + le16_to_cpu(id->nawupf)) * bs;
+ if (id->nabspf)
boundary = (le16_to_cpu(id->nabspf) + 1) * bs;
+ } else {
+ /*
+ * Use the controller wide atomic write unit. This sucks
+ * because the limit is defined in terms of logical blocks while
+ * namespaces can have different formats, and because there is
+ * no clear language in the specification prohibiting different
+ * values for different controllers in the subsystem.
+ */
+ atomic_bs = (1 + ns->ctrl->awupf) * bs;
+ }
+
+ if (!ns->ctrl->subsys->atomic_bs) {
+ ns->ctrl->subsys->atomic_bs = atomic_bs;
+ } else if (ns->ctrl->subsys->atomic_bs != atomic_bs) {
+ dev_err_ratelimited(ns->ctrl->device,
+ "%s: Inconsistent Atomic Write Size, Namespace will not be added: Subsystem=%d bytes, Controller/Namespace=%d bytes\n",
+ ns->disk ? ns->disk->disk_name : "?",
+ ns->ctrl->subsys->atomic_bs,
+ atomic_bs);
}
+
lim->atomic_write_hw_max = atomic_bs;
lim->atomic_write_hw_boundary = boundary;
lim->atomic_write_hw_unit_min = bs;
lim->atomic_write_hw_unit_max = rounddown_pow_of_two(atomic_bs);
+ return atomic_bs;
}
static u32 nvme_max_drv_segments(struct nvme_ctrl *ctrl)
@@ -2046,34 +2076,8 @@ static bool nvme_update_disk_info(struct nvme_ns *ns, struct nvme_id_ns *id,
valid = false;
}
- atomic_bs = phys_bs = bs;
- if (id->nabo == 0) {
- /*
- * Bit 1 indicates whether NAWUPF is defined for this namespace
- * and whether it should be used instead of AWUPF. If NAWUPF ==
- * 0 then AWUPF must be used instead.
- */
- if (id->nsfeat & NVME_NS_FEAT_ATOMICS && id->nawupf)
- atomic_bs = (1 + le16_to_cpu(id->nawupf)) * bs;
- else
- atomic_bs = (1 + ns->ctrl->awupf) * bs;
-
- /*
- * Set subsystem atomic bs.
- */
- if (ns->ctrl->subsys->atomic_bs) {
- if (atomic_bs != ns->ctrl->subsys->atomic_bs) {
- dev_err_ratelimited(ns->ctrl->device,
- "%s: Inconsistent Atomic Write Size, Namespace will not be added: Subsystem=%d bytes, Controller/Namespace=%d bytes\n",
- ns->disk ? ns->disk->disk_name : "?",
- ns->ctrl->subsys->atomic_bs,
- atomic_bs);
- }
- } else
- ns->ctrl->subsys->atomic_bs = atomic_bs;
-
- nvme_update_atomic_write_disk_info(ns, id, lim, bs, atomic_bs);
- }
+ phys_bs = bs;
+ atomic_bs = nvme_configure_atomic_write(ns, id, lim, bs);
if (id->nsfeat & NVME_NS_FEAT_IO_OPT) {
/* NPWG = Namespace Preferred Write Granularity */
@@ -2097,6 +2101,7 @@ static bool nvme_update_disk_info(struct nvme_ns *ns, struct nvme_id_ns *id,
lim->max_write_zeroes_sectors = UINT_MAX;
else
lim->max_write_zeroes_sectors = ns->ctrl->max_zeroes_sectors;
+ nvme_config_discard(ns, lim);
return valid;
}
@@ -2171,6 +2176,7 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,
struct nvme_id_ns *id;
sector_t capacity;
unsigned lbaf;
+ bool valid;
int ret;
ret = nvme_identify_ns(ns->ctrl, info->nsid, &id);
@@ -2207,20 +2213,22 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,
nvme_set_ctrl_limits(ns->ctrl, &lim);
nvme_configure_metadata(ns->ctrl, ns->head, id, nvm, info);
nvme_set_chunk_sectors(ns, id, &lim);
- if (!nvme_update_disk_info(ns, id, &lim))
- capacity = 0;
+ valid = nvme_update_disk_info(ns, id, &lim);
/*
* Validate the max atomic write size fits within the subsystem's
* atomic write capabilities.
*/
if (lim.atomic_write_hw_max > ns->ctrl->subsys->atomic_bs) {
- blk_mq_unfreeze_queue(ns->disk->queue, memflags);
+ queue_limits_cancel_update(ns->disk->queue);
+ blk_mq_unfreeze_queue(ns->disk->queue);
ret = -ENXIO;
goto out;
}
- nvme_config_discard(ns, &lim);
+ if (!valid)
+ capacity = 0;
+
if (IS_ENABLED(CONFIG_BLK_DEV_ZONED) &&
ns->head->ids.csi == NVME_CSI_ZNS)
nvme_update_zone_info(ns, &lim, &zi);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 6.12.y 3/4] nvme: fix atomic write size validation
2026-09-11 0:15 ` [PATCH 6.12.y 1/4] nvme: all namespaces in a subsystem must adhere to a common atomic write size Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 2/4] nvme: refactor the atomic write unit detection Sasha Levin
@ 2026-09-11 0:15 ` Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 4/4] nvme: skip the zoned limits update if the zone info query failed Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-09-11 0:15 UTC (permalink / raw)
To: stable
Cc: Christoph Hellwig, Yi Zhang, Luis Chamberlain, John Garry,
Sasha Levin
From: Christoph Hellwig <hch@lst.de>
[ Upstream commit f46d273449ba65afd53f3dd8fe0182c9df877e08 ]
Don't mix the namespace and controller values, and validate the
per-controller limit when probing the controller. This avoid spurious
failures for controllers with namespaces that have different namespaces
with different logical block sizes, or report the per-namespace values
only for some namespaces.
It also fixes a missing queue_limits_cancel_update in an error path by
removing that error path.
Fixes: 8695f060a029 ("nvme: all namespaces in a subsystem must adhere to a common atomic write size")
Reported-by: Yi Zhang <yi.zhang@redhat.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Reviewed-by: John Garry <john.g.garry@oracle.com>
Tested-by: Yi Zhang <yi.zhang@redhat.com>
Stable adaptation for 6.12:
- Remove the atomic-size rejection together with its locally adapted
queue_limits_cancel_update() and one-argument queue unfreeze calls.
Validation now happens during controller identification, as upstream.
- Keep nvme_config_discard() in nvme_update_disk_info(), where the
preceding dependency moved it. Do not restore a duplicate call here.
- Preserve the saved disk-info validity result and capacity handling,
leaving the zoned update insertion point available for target
3838e80fcfb32e62baffb63c6dc0a60153665a4d.
No additional functions are introduced.
Stable-dep-of: 3838e80fcfb3 ("nvme: skip the zoned limits update if the zone info query failed")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/nvme/host/core.c | 34 +++++++++++-----------------------
drivers/nvme/host/nvme.h | 3 +--
2 files changed, 12 insertions(+), 25 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index deffad78d1dcf..3e36b814fbe6a 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2021,17 +2021,7 @@ static u32 nvme_configure_atomic_write(struct nvme_ns *ns,
* no clear language in the specification prohibiting different
* values for different controllers in the subsystem.
*/
- atomic_bs = (1 + ns->ctrl->awupf) * bs;
- }
-
- if (!ns->ctrl->subsys->atomic_bs) {
- ns->ctrl->subsys->atomic_bs = atomic_bs;
- } else if (ns->ctrl->subsys->atomic_bs != atomic_bs) {
- dev_err_ratelimited(ns->ctrl->device,
- "%s: Inconsistent Atomic Write Size, Namespace will not be added: Subsystem=%d bytes, Controller/Namespace=%d bytes\n",
- ns->disk ? ns->disk->disk_name : "?",
- ns->ctrl->subsys->atomic_bs,
- atomic_bs);
+ atomic_bs = (1 + ns->ctrl->subsys->awupf) * bs;
}
lim->atomic_write_hw_max = atomic_bs;
@@ -2215,17 +2205,6 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,
nvme_set_chunk_sectors(ns, id, &lim);
valid = nvme_update_disk_info(ns, id, &lim);
- /*
- * Validate the max atomic write size fits within the subsystem's
- * atomic write capabilities.
- */
- if (lim.atomic_write_hw_max > ns->ctrl->subsys->atomic_bs) {
- queue_limits_cancel_update(ns->disk->queue);
- blk_mq_unfreeze_queue(ns->disk->queue);
- ret = -ENXIO;
- goto out;
- }
-
if (!valid)
capacity = 0;
@@ -3039,6 +3018,7 @@ static int nvme_init_subsystem(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id)
memcpy(subsys->model, id->mn, sizeof(subsys->model));
subsys->vendor_id = le16_to_cpu(id->vid);
subsys->cmic = id->cmic;
+ subsys->awupf = le16_to_cpu(id->awupf);
/* Versions prior to 1.4 don't necessarily report a valid type */
if (id->cntrltype == NVME_CTRL_DISC ||
@@ -3368,6 +3348,15 @@ static int nvme_init_identify(struct nvme_ctrl *ctrl)
if (ret)
goto out_free;
}
+
+ if (le16_to_cpu(id->awupf) != ctrl->subsys->awupf) {
+ dev_err_ratelimited(ctrl->device,
+ "inconsistent AWUPF, controller not added (%u/%u).\n",
+ le16_to_cpu(id->awupf), ctrl->subsys->awupf);
+ ret = -EINVAL;
+ goto out_free;
+ }
+
memcpy(ctrl->subsys->firmware_rev, id->fr,
sizeof(ctrl->subsys->firmware_rev));
@@ -3463,7 +3452,6 @@ static int nvme_init_identify(struct nvme_ctrl *ctrl)
dev_pm_qos_expose_latency_tolerance(ctrl->device);
else if (!ctrl->apst_enabled && prev_apst_enabled)
dev_pm_qos_hide_latency_tolerance(ctrl->device);
- ctrl->awupf = le16_to_cpu(id->awupf);
out_free:
kfree(id);
return ret;
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 8b4c0e17bb4e7..5985cdd4c02d6 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -410,7 +410,6 @@ struct nvme_ctrl {
enum nvme_ctrl_type cntrltype;
enum nvme_dctype dctype;
- u16 awupf; /* 0's based value. */
};
static inline enum nvme_ctrl_state nvme_ctrl_state(struct nvme_ctrl *ctrl)
@@ -443,11 +442,11 @@ struct nvme_subsystem {
u8 cmic;
enum nvme_subsys_type subtype;
u16 vendor_id;
+ u16 awupf; /* 0's based value. */
struct ida ns_ida;
#ifdef CONFIG_NVME_MULTIPATH
enum nvme_iopolicy iopolicy;
#endif
- u32 atomic_bs;
};
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 6.12.y 4/4] nvme: skip the zoned limits update if the zone info query failed
2026-09-11 0:15 ` [PATCH 6.12.y 1/4] nvme: all namespaces in a subsystem must adhere to a common atomic write size Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 2/4] nvme: refactor the atomic write unit detection Sasha Levin
2026-09-11 0:15 ` [PATCH 6.12.y 3/4] nvme: fix atomic write size validation Sasha Levin
@ 2026-09-11 0:15 ` Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-09-11 0:15 UTC (permalink / raw)
To: stable; +Cc: Chao Shi, Weidong Zhu, Keith Busch, Christoph Hellwig,
Sasha Levin
From: Chao Shi <coshi036@gmail.com>
[ Upstream commit 3838e80fcfb32e62baffb63c6dc0a60153665a4d ]
nvme_query_zone_info() returns either a negative errno or a positive
NVMe status code, but nvme_update_ns_info_block() only tests for the
negative case:
ret = nvme_query_zone_info(ns, lbaf, &zi);
if (ret < 0)
goto out;
If the device fails the Identify Namespace (I/O Command Set specific)
command, or the Identify Controller command issued by
nvme_set_max_append(), the positive status falls through and setup
continues with the zero-initialized zone info. nvme_update_zone_info()
then marks the queue zoned with chunk_sectors and ns->head->zsze set to
zero.
blk_validate_zoned_limits() does not check chunk_sectors, so the limits
commit succeeds. blk_revalidate_disk_zones() does reject the zero zone
size, but by then the limits are live and nothing rolls them back, so
I/O keeps being submitted to a zoned queue with a zero zone size and
disk_zone_no() shifts by ilog2(0):
nvme0n1: Invalid non power of two zone size (0)
UBSAN: shift-out-of-bounds in include/linux/blkdev.h:747:16
shift exponent -1 is negative
disk_zone_no include/linux/blkdev.h:747 [inline]
bio_straddles_zones include/linux/blkdev.h:1058 [inline]
blk_zone_wplug_handle_write block/blk-zoned.c:1423 [inline]
blk_zone_plug_bio.cold+0x25/0x1c8 block/blk-zoned.c:1605
blk_mq_submit_bio+0x18fb/0x2870 block/blk-mq.c:3196
submit_bh_wbc+0x575/0x740 fs/buffer.c:2824
__block_write_full_folio+0x728/0xdd0 fs/buffer.c:1933
Any device, firmware or NVMe-oF target that fails this one command
reaches this.
Skip the zoned limits update in that case, and log which of the two
things happened: during a revalidation the queue keeps the zone
geometry it was last validated with, and on a first scan the namespace
is registered without zoned limits, so that it is still available as a
handle for admin commands. Neither of the paths in
nvme_query_zone_info() that return a positive status logs anything, so
the failure would otherwise be silent.
zi.zone_size is an exact indicator: every path that returns a positive
status returns before it is assigned, and after that the only failure
left is -ENODEV, which the caller already handles.
Found by FuzzNvme.
Fixes: c85c9ab926a5 ("nvme: split nvme_update_zone_info")
Cc: stable@vger.kernel.org
Cc: Weidong Zhu <weizhu@fiu.edu>
Suggested-by: Keith Busch <kbusch@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Chao Shi <coshi036@gmail.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/nvme/host/core.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 3e36b814fbe6a..917370e290668 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2208,9 +2208,26 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,
if (!valid)
capacity = 0;
+ /*
+ * A failed zone info query leaves zi zero-initialized, so skip the
+ * zoned limits update instead of configuring the queue from it.
+ * During a revalidation that keeps the zone geometry the queue was
+ * last validated with; on a first scan the namespace is registered
+ * without zoned limits, so that it is still available as a handle
+ * for admin commands.
+ */
if (IS_ENABLED(CONFIG_BLK_DEV_ZONED) &&
- ns->head->ids.csi == NVME_CSI_ZNS)
- nvme_update_zone_info(ns, &lim, &zi);
+ ns->head->ids.csi == NVME_CSI_ZNS) {
+ if (zi.zone_size)
+ nvme_update_zone_info(ns, &lim, &zi);
+ else
+ dev_warn(ns->ctrl->device,
+ "zone info query failed for nsid %u, %s\n",
+ ns->head->ns_id,
+ blk_queue_is_zoned(ns->disk->queue) ?
+ "keeping the previous zone limits" :
+ "not enabling zoned mode");
+ }
if (ns->ctrl->vwc & NVME_CTRL_VWC_PRESENT)
lim.features |= BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA;
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread