* [PATCH v1 0/2] nvme: use local limits for namespace heads
@ 2026-07-20 2:22 Yao Sang
2026-07-20 2:22 ` [PATCH v1 1/2] nvme: factor namespace-head queue-limit update Yao Sang
2026-07-20 2:22 ` [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads Yao Sang
0 siblings, 2 replies; 5+ messages in thread
From: Yao Sang @ 2026-07-20 2:22 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: Yao Sang
Hi everyone,
NVMe native multipath refreshes namespace-head queue limits whenever
namespace information is updated. The current path uses
queue_limits_stack_bdev(), then overrides selected NVMe configuration
fields afterwards.
queue_limits_stack_bdev() also applies offset and alignment handling
intended for stacked devices. An NVMe namespace head has a different
relationship with its underlying devices: every path reaches the same
namespace rather than a separately mapped lower device. Its limit update
therefore needs to refresh Identify-derived configuration, combine
path-dependent request limits conservatively, and retain user-set queue
limits.
This series makes that distinction explicit in two steps. Patch 1 moves
the existing namespace-head update into a local helper without changing
its behavior. Patch 2 then replaces the generic stacking operation with
NVMe-specific handling for configuration, features, and request limits.
Testing:
- blktests nvme/004, 005, 006, 008, 010, 012, 014, 016, 017, 018, 019,
021, 022, 023, 025, 026, 027, 028, 029, 030, 031, 040, 041, 042, 043,
044, 045, 049, 051, 052, 053, 054, 057, 058, 059,
065, 067 and 068 passed.
- blktests zbd/001, 003, 004, 005, 006, 008, 009, 011, 012, and 013
passed.
- blktests block/003, 004, and 012, and scsi/009 and 010 passed.
- xfstests xfs/643, xfs/646, generic/765, and generic/773 passed.
Changes since RFC:
- rebase from nvme-7.2 to nvme-7.3;
- keep the thread kernel-only
Link: https://lore.kernel.org/r/20260709084826.3208740-1-sangyao@kylinos.cn
Thanks,
Yao
Yao Sang (2):
nvme: factor namespace-head queue-limit update
nvme: stop using queue_limits_stack_bdev for namespace heads
drivers/nvme/host/core.c | 196 ++++++++++++++++++++++++++++-----------
1 file changed, 143 insertions(+), 53 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 1/2] nvme: factor namespace-head queue-limit update
2026-07-20 2:22 [PATCH v1 0/2] nvme: use local limits for namespace heads Yao Sang
@ 2026-07-20 2:22 ` Yao Sang
2026-07-20 14:42 ` Christoph Hellwig
2026-07-20 2:22 ` [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads Yao Sang
1 sibling, 1 reply; 5+ messages in thread
From: Yao Sang @ 2026-07-20 2:22 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: Yao Sang
Move the namespace head queue-limit update out of nvme_update_ns_info().
The new helper keeps the current queue_limits_stack_bdev() based behavior
intact, including zoned resource handling, write-stream assignment,
integrity setup, capacity and readonly updates, path revalidation, and
namespace-head zone revalidation.
Keep queue-limit commit failures on the existing short-circuit path so
capacity and namespace-head state are only updated after a successful
limits update.
The helper gives namespace-head queue-limit updates a single NVMe-local
entry point while keeping the namespace information refresh sequencing
unchanged.
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
drivers/nvme/host/core.c | 104 +++++++++++++++++++++------------------
1 file changed, 56 insertions(+), 48 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index b7293fe66540..e351cb615b10 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2526,6 +2526,60 @@ static void nvme_stack_zone_resources(struct queue_limits *t,
min_not_zero(t->max_active_zones, b->max_active_zones);
}
+static int nvme_update_ns_head_limits(struct nvme_ns *ns,
+ struct nvme_ns_info *info, bool unsupported)
+{
+ struct queue_limits *ns_lim = &ns->disk->queue->limits;
+ struct request_queue *head_q = ns->head->disk->queue;
+ struct queue_limits lim;
+ unsigned int memflags;
+ int ret;
+
+ lim = queue_limits_start_update(head_q);
+ memflags = blk_mq_freeze_queue(head_q);
+ /*
+ * queue_limits mixes values that are the hardware limitations
+ * for bio splitting with what is the device configuration.
+ *
+ * For NVMe the device configuration can change after e.g. a
+ * Format command, and we really want to pick up the new format
+ * value here. But we must still stack the queue limits to the
+ * least common denominator for multipathing to split the bios
+ * properly.
+ *
+ * To work around this, we explicitly set the device
+ * configuration to those that we just queried, but only stack
+ * the splitting limits in to make sure we still obey possibly
+ * lower limitations of other controllers.
+ */
+ lim.logical_block_size = ns_lim->logical_block_size;
+ lim.physical_block_size = ns_lim->physical_block_size;
+ lim.io_min = ns_lim->io_min;
+ lim.io_opt = ns_lim->io_opt;
+ queue_limits_stack_bdev(&lim, ns->disk->part0, 0,
+ ns->head->disk->disk_name);
+ if (lim.features & BLK_FEAT_ZONED)
+ nvme_stack_zone_resources(&lim, ns_lim);
+ if (unsupported)
+ ns->head->disk->flags |= GENHD_FL_HIDDEN;
+ else
+ nvme_init_integrity(ns->head, &lim, info);
+ lim.max_write_streams = ns_lim->max_write_streams;
+ lim.write_stream_granularity = ns_lim->write_stream_granularity;
+ ret = queue_limits_commit_update(head_q, &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(head_q, memflags);
+ return ret;
+}
+
static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info)
{
bool unsupported = false;
@@ -2564,54 +2618,8 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info)
ret = 0;
}
- if (!ret && nvme_ns_head_multipath(ns->head)) {
- struct queue_limits *ns_lim = &ns->disk->queue->limits;
- struct queue_limits lim;
- unsigned int memflags;
-
- lim = queue_limits_start_update(ns->head->disk->queue);
- memflags = blk_mq_freeze_queue(ns->head->disk->queue);
- /*
- * queue_limits mixes values that are the hardware limitations
- * for bio splitting with what is the device configuration.
- *
- * For NVMe the device configuration can change after e.g. a
- * Format command, and we really want to pick up the new format
- * value here. But we must still stack the queue limits to the
- * least common denominator for multipathing to split the bios
- * properly.
- *
- * To work around this, we explicitly set the device
- * configuration to those that we just queried, but only stack
- * the splitting limits in to make sure we still obey possibly
- * lower limitations of other controllers.
- */
- lim.logical_block_size = ns_lim->logical_block_size;
- lim.physical_block_size = ns_lim->physical_block_size;
- lim.io_min = ns_lim->io_min;
- lim.io_opt = ns_lim->io_opt;
- queue_limits_stack_bdev(&lim, ns->disk->part0, 0,
- ns->head->disk->disk_name);
- if (lim.features & BLK_FEAT_ZONED)
- nvme_stack_zone_resources(&lim, ns_lim);
- if (unsupported)
- ns->head->disk->flags |= GENHD_FL_HIDDEN;
- else
- nvme_init_integrity(ns->head, &lim, 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);
- }
+ if (!ret && nvme_ns_head_multipath(ns->head))
+ ret = nvme_update_ns_head_limits(ns, info, unsupported);
return ret;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads
2026-07-20 2:22 [PATCH v1 0/2] nvme: use local limits for namespace heads Yao Sang
2026-07-20 2:22 ` [PATCH v1 1/2] nvme: factor namespace-head queue-limit update Yao Sang
@ 2026-07-20 2:22 ` Yao Sang
2026-07-20 14:46 ` Christoph Hellwig
1 sibling, 1 reply; 5+ messages in thread
From: Yao Sang @ 2026-07-20 2:22 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: Yao Sang
NVMe namespace heads are not generic stacked block devices. Updating a
head with queue_limits_stack_bdev() pulls in bdev offset, alignment, and
generic stacking policy that belongs to mapped devices, while NVMe needs
to refresh namespace Identify data and preserve head user caps.
Add NVMe-local helpers that copy refreshed namespace configuration, keep
head user caps from queue_limits_start_update(), preserve the existing
write-cache and compatibility feature inheritance, and merge request
limits conservatively.
Execution features and request limits continue to accumulate
conservatively as paths are refreshed. This change does not attempt to
widen head limits when a restrictive path disappears; recomputing limits
from the complete live path set is a separate problem.
Keep the helper local to NVMe so its field ownership remains explicit and
does not impose NVMe namespace-head policy on mapped devices.
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
drivers/nvme/host/core.c | 142 ++++++++++++++++++++++++++++++---------
1 file changed, 112 insertions(+), 30 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index e351cb615b10..320bfdf39616 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2518,12 +2518,117 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,
return ret;
}
-static void nvme_stack_zone_resources(struct queue_limits *t,
- const struct queue_limits *b)
+static void nvme_apply_ns_head_identify_limits(struct queue_limits *lim,
+ const struct queue_limits *ns_lim)
{
- t->max_open_zones = min_not_zero(t->max_open_zones, b->max_open_zones);
- t->max_active_zones =
- min_not_zero(t->max_active_zones, b->max_active_zones);
+ /*
+ * Identify-derived namespace/head attributes come from the refreshed
+ * NVMe namespace limits, not from generic mapped-device stacking.
+ */
+ lim->features &= ~(BLK_FEAT_ZONED | BLK_FEAT_ATOMIC_WRITES);
+ lim->features |= ns_lim->features & BLK_FEAT_ZONED;
+ lim->logical_block_size = ns_lim->logical_block_size;
+ lim->physical_block_size = ns_lim->physical_block_size;
+ lim->io_min = ns_lim->io_min;
+ lim->io_opt = ns_lim->io_opt;
+ lim->chunk_sectors = ns_lim->chunk_sectors;
+ lim->discard_granularity = ns_lim->discard_granularity;
+ lim->max_hw_discard_sectors = ns_lim->max_hw_discard_sectors;
+ lim->max_discard_segments = ns_lim->max_discard_segments;
+ lim->zone_write_granularity = ns_lim->zone_write_granularity;
+ lim->max_hw_zone_append_sectors = ns_lim->max_hw_zone_append_sectors;
+ lim->max_write_streams = ns_lim->max_write_streams;
+ lim->write_stream_granularity = ns_lim->write_stream_granularity;
+
+ if ((ns_lim->features & BLK_FEAT_ATOMIC_WRITES) &&
+ ns_lim->atomic_write_hw_unit_min) {
+ lim->features |= BLK_FEAT_ATOMIC_WRITES;
+ lim->atomic_write_hw_max = ns_lim->atomic_write_hw_max;
+ lim->atomic_write_hw_unit_min =
+ ns_lim->atomic_write_hw_unit_min;
+ lim->atomic_write_hw_unit_max =
+ ns_lim->atomic_write_hw_unit_max;
+ lim->atomic_write_hw_boundary =
+ ns_lim->atomic_write_hw_boundary;
+ } else {
+ lim->atomic_write_hw_max = 0;
+ lim->atomic_write_hw_unit_min = 0;
+ lim->atomic_write_hw_unit_max = 0;
+ lim->atomic_write_hw_boundary = 0;
+ }
+
+ if (lim->features & BLK_FEAT_ZONED) {
+ lim->max_open_zones = min_not_zero(lim->max_open_zones,
+ ns_lim->max_open_zones);
+ lim->max_active_zones =
+ min_not_zero(lim->max_active_zones,
+ ns_lim->max_active_zones);
+ } else {
+ lim->max_open_zones = 0;
+ lim->max_active_zones = 0;
+ lim->max_zone_append_sectors = 0;
+ }
+}
+
+static void nvme_apply_ns_head_feature_limits(struct queue_limits *lim,
+ const struct queue_limits *ns_lim)
+{
+ /*
+ * Keep the existing inheritance for compatibility feature bits.
+ * Path execution features are handled separately below.
+ */
+ lim->features |= ns_lim->features &
+ (BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA |
+ BLK_FEAT_ROTATIONAL | BLK_FEAT_STABLE_WRITES);
+
+ /*
+ * Head-level execution capabilities are only safe if every usable path
+ * supports them.
+ */
+ if (!(ns_lim->features & BLK_FEAT_NOWAIT))
+ lim->features &= ~BLK_FEAT_NOWAIT;
+ if (!(ns_lim->features & BLK_FEAT_POLL))
+ lim->features &= ~BLK_FEAT_POLL;
+ if (!(ns_lim->features & BLK_FEAT_PCI_P2PDMA))
+ lim->features &= ~BLK_FEAT_PCI_P2PDMA;
+}
+
+static void nvme_apply_ns_head_request_limits(struct queue_limits *lim,
+ const struct queue_limits *ns_lim)
+{
+ lim->max_hw_sectors = min_not_zero(lim->max_hw_sectors,
+ ns_lim->max_hw_sectors);
+ lim->max_dev_sectors = min_not_zero(lim->max_dev_sectors,
+ ns_lim->max_dev_sectors);
+ lim->max_write_zeroes_sectors =
+ min(lim->max_write_zeroes_sectors,
+ ns_lim->max_write_zeroes_sectors);
+ lim->max_hw_wzeroes_unmap_sectors =
+ min(lim->max_hw_wzeroes_unmap_sectors,
+ ns_lim->max_hw_wzeroes_unmap_sectors);
+ lim->seg_boundary_mask = min_not_zero(lim->seg_boundary_mask,
+ ns_lim->seg_boundary_mask);
+ lim->virt_boundary_mask = min_not_zero(lim->virt_boundary_mask,
+ ns_lim->virt_boundary_mask);
+ lim->max_segments = min_not_zero(lim->max_segments,
+ ns_lim->max_segments);
+ lim->max_integrity_segments =
+ min_not_zero(lim->max_integrity_segments,
+ ns_lim->max_integrity_segments);
+ lim->max_segment_size = min_not_zero(lim->max_segment_size,
+ ns_lim->max_segment_size);
+ lim->dma_alignment = max(lim->dma_alignment, ns_lim->dma_alignment);
+ lim->max_secure_erase_sectors =
+ min_not_zero(lim->max_secure_erase_sectors,
+ ns_lim->max_secure_erase_sectors);
+}
+
+static void nvme_apply_ns_head_limits(struct queue_limits *lim,
+ const struct queue_limits *ns_lim)
+{
+ nvme_apply_ns_head_identify_limits(lim, ns_lim);
+ nvme_apply_ns_head_feature_limits(lim, ns_lim);
+ nvme_apply_ns_head_request_limits(lim, ns_lim);
}
static int nvme_update_ns_head_limits(struct nvme_ns *ns,
@@ -2537,35 +2642,12 @@ static int nvme_update_ns_head_limits(struct nvme_ns *ns,
lim = queue_limits_start_update(head_q);
memflags = blk_mq_freeze_queue(head_q);
- /*
- * queue_limits mixes values that are the hardware limitations
- * for bio splitting with what is the device configuration.
- *
- * For NVMe the device configuration can change after e.g. a
- * Format command, and we really want to pick up the new format
- * value here. But we must still stack the queue limits to the
- * least common denominator for multipathing to split the bios
- * properly.
- *
- * To work around this, we explicitly set the device
- * configuration to those that we just queried, but only stack
- * the splitting limits in to make sure we still obey possibly
- * lower limitations of other controllers.
- */
- lim.logical_block_size = ns_lim->logical_block_size;
- lim.physical_block_size = ns_lim->physical_block_size;
- lim.io_min = ns_lim->io_min;
- lim.io_opt = ns_lim->io_opt;
- queue_limits_stack_bdev(&lim, ns->disk->part0, 0,
- ns->head->disk->disk_name);
- if (lim.features & BLK_FEAT_ZONED)
- nvme_stack_zone_resources(&lim, ns_lim);
+
+ nvme_apply_ns_head_limits(&lim, ns_lim);
if (unsupported)
ns->head->disk->flags |= GENHD_FL_HIDDEN;
else
nvme_init_integrity(ns->head, &lim, info);
- lim.max_write_streams = ns_lim->max_write_streams;
- lim.write_stream_granularity = ns_lim->write_stream_granularity;
ret = queue_limits_commit_update(head_q, &lim);
if (ret)
goto unfreeze_head_queue;
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/2] nvme: factor namespace-head queue-limit update
2026-07-20 2:22 ` [PATCH v1 1/2] nvme: factor namespace-head queue-limit update Yao Sang
@ 2026-07-20 14:42 ` Christoph Hellwig
0 siblings, 0 replies; 5+ messages in thread
From: Christoph Hellwig @ 2026-07-20 14:42 UTC (permalink / raw)
To: Yao Sang; +Cc: kbusch, axboe, hch, sagi, linux-nvme
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads
2026-07-20 2:22 ` [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads Yao Sang
@ 2026-07-20 14:46 ` Christoph Hellwig
0 siblings, 0 replies; 5+ messages in thread
From: Christoph Hellwig @ 2026-07-20 14:46 UTC (permalink / raw)
To: Yao Sang; +Cc: kbusch, axboe, hch, sagi, linux-nvme
On Mon, Jul 20, 2026 at 10:22:05AM +0800, Yao Sang wrote:
> NVMe namespace heads are not generic stacked block devices.
Not many things actually are, which leads to every recurring problems
with the stacking scheme..
> Execution features and request limits continue to accumulate
> conservatively as paths are refreshed.
What is this supposed to mean?
> Keep the helper local to NVMe so its field ownership remains explicit and
What is "field ownership" supposed to mean?
> does not impose NVMe namespace-head policy on mapped devices.
I think you need to clearly state in what way we'll want to diverge
here.
> +static void nvme_apply_ns_head_identify_limits(struct queue_limits *lim,
> + const struct queue_limits *ns_lim)
Two tab indents, please.
> {
> - t->max_open_zones = min_not_zero(t->max_open_zones, b->max_open_zones);
> - t->max_active_zones =
> - min_not_zero(t->max_active_zones, b->max_active_zones);
> + /*
> + * Identify-derived namespace/head attributes come from the refreshed
> + * NVMe namespace limits, not from generic mapped-device stacking.
> + */
> + lim->features &= ~(BLK_FEAT_ZONED | BLK_FEAT_ATOMIC_WRITES);
> + lim->features |= ns_lim->features & BLK_FEAT_ZONED;
> + lim->logical_block_size = ns_lim->logical_block_size;
> + lim->physical_block_size = ns_lim->physical_block_size;
A lot of this still duplicates the core code and creates a nightmare
when we add new fields.
Can you try to refactor the core code into lower level building
blocks and reuse as much as possible while also making the policy
differences clear?
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-20 14:47 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 2:22 [PATCH v1 0/2] nvme: use local limits for namespace heads Yao Sang
2026-07-20 2:22 ` [PATCH v1 1/2] nvme: factor namespace-head queue-limit update Yao Sang
2026-07-20 14:42 ` Christoph Hellwig
2026-07-20 2:22 ` [PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads Yao Sang
2026-07-20 14:46 ` Christoph Hellwig
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox