* [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking
@ 2026-08-21 10:10 Yao Sang
2026-08-21 10:10 ` [PATCH v3 1/5] nvme: factor namespace-head queue-limit update Yao Sang
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
blk_stack_limits() stacks limits for a top-device range mapped onto a
bottom device. Same-LBA multipath instead needs to stack limits across
paths without applying mapped-range topology calculations.
Factor the common topology, path and atomic write code into helpers. Add
block helpers for same-LBA multipath and use them for NVMe namespace heads.
SCSI multipath can use the same helpers.
Changes since v2:
- replace the exported low-level helpers with block helpers for same-LBA
multipath;
- keep mapped-range and start-dependent checks inside blk_stack_limits().
Review of v2 noted that fixed namespace limits should not be updated for
each path. V3 separates these limits from path limit stacking, but keeps
the existing NVMe update behavior.
Changing this also requires explicit namespace-head revalidation after
events such as Format NVM. That work is left for a follow-up.
Tests:
- 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.
Link to v2:
https://lore.kernel.org/r/20260806024658.4193386-1-sangyao@kylinos.cn
Yao Sang (5):
nvme: factor namespace-head queue-limit update
block: factor mapped-range topology out of blk_stack_limits
block: factor path limits out of blk_stack_limits
block: factor atomic write hardware limit stacking
block, nvme: add same-LBA multipath limit helpers
block/blk-settings.c | 252 ++++++++++++++++++++++------------
drivers/nvme/host/core.c | 83 ++++-------
drivers/nvme/host/multipath.c | 2 +
include/linux/blkdev.h | 3 +
4 files changed, 202 insertions(+), 138 deletions(-)
base-commit: f1a8846e06388113dfdbb89dee005083fa9afdf9
--
2.25.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/5] nvme: factor namespace-head queue-limit update
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
@ 2026-08-21 10:10 ` Yao Sang
2026-08-21 10:10 ` [PATCH v3 2/5] block: factor mapped-range topology out of blk_stack_limits Yao Sang
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
Move the namespace-head queue-limit update out of nvme_update_ns_info().
The update is self-contained, and moving it into a separate helper makes
the namespace scan easier to follow.
There is no behavior change.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
drivers/nvme/host/core.c | 103 +++++++++++++++++++++------------------
1 file changed, 55 insertions(+), 48 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 1322c678f4eb..8e2b44ed4366 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2538,6 +2538,59 @@ 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 hardware limitations for bio splitting with 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;
@@ -2576,54 +2629,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] 10+ messages in thread
* [PATCH v3 2/5] block: factor mapped-range topology out of blk_stack_limits
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
2026-08-21 10:10 ` [PATCH v3 1/5] nvme: factor namespace-head queue-limit update Yao Sang
@ 2026-08-21 10:10 ` Yao Sang
2026-08-21 10:10 ` [PATCH v3 3/5] block: factor path limits " Yao Sang
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
Block sizes, I/O granularities, chunk boundaries and alignment offsets
must be stacked and checked when a top-device range is mapped onto a
bottom device.
Factor this code into blk_stack_topology_limits(). Keep rounding of the
maximum sector limits there because it depends on the resulting logical
block size.
There is no behavior change.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
block/blk-settings.c | 168 +++++++++++++++++++++++--------------------
1 file changed, 92 insertions(+), 76 deletions(-)
diff --git a/block/blk-settings.c b/block/blk-settings.c
index 8274631290db..9e7cdaaefbca 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -756,6 +756,95 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
t->atomic_write_hw_boundary = 0;
}
+/*
+ * Stack block sizes, I/O granularities, chunk boundaries and alignment for a
+ * bottom-device range mapped at @start. Round maximum sector limits after the
+ * resulting logical block size is known.
+ */
+static int blk_stack_topology_limits(struct queue_limits *t,
+ const struct queue_limits *b, sector_t start)
+{
+ unsigned int top, bottom, alignment;
+ int ret = 0;
+
+ t->flags |= b->flags & BLK_FLAG_MISALIGNED;
+
+ alignment = queue_limit_alignment_offset(b, start);
+
+ /*
+ * The bottom device has a different alignment. Check that it is
+ * compatible with the current top alignment.
+ */
+ if (t->alignment_offset != alignment) {
+ top = max(t->physical_block_size, t->io_min) + t->alignment_offset;
+ bottom = max(b->physical_block_size, b->io_min) + alignment;
+
+ /* Verify that top and bottom intervals line up. */
+ if (max(top, bottom) % min(top, bottom)) {
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+ }
+
+ t->logical_block_size = max(t->logical_block_size,
+ b->logical_block_size);
+ t->physical_block_size = max(t->physical_block_size,
+ b->physical_block_size);
+ t->io_min = max(t->io_min, b->io_min);
+ t->io_opt = lcm_not_zero(t->io_opt, b->io_opt);
+
+ /* Set non-power-of-2 compatible chunk_sectors boundary. */
+ if (b->chunk_sectors)
+ t->chunk_sectors = gcd(t->chunk_sectors, b->chunk_sectors);
+
+ /* Physical block size a multiple of the logical block size? */
+ if (t->physical_block_size & (t->logical_block_size - 1)) {
+ t->physical_block_size = t->logical_block_size;
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+
+ /* Minimum I/O a multiple of the physical block size? */
+ if (t->io_min & (t->physical_block_size - 1)) {
+ t->io_min = t->physical_block_size;
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+
+ /* Optimal I/O a multiple of the physical block size? */
+ if (t->io_opt & (t->physical_block_size - 1)) {
+ t->io_opt = 0;
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+
+ /* chunk_sectors a multiple of the physical block size? */
+ if (t->chunk_sectors % (t->physical_block_size >> SECTOR_SHIFT)) {
+ t->chunk_sectors = 0;
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+
+ /* Find lowest common alignment_offset. */
+ t->alignment_offset = lcm_not_zero(t->alignment_offset, alignment) %
+ max(t->physical_block_size, t->io_min);
+
+ /* Verify that new alignment_offset is on a logical block boundary. */
+ if (t->alignment_offset & (t->logical_block_size - 1)) {
+ t->flags |= BLK_FLAG_MISALIGNED;
+ ret = -1;
+ }
+
+ t->max_sectors = blk_round_down_sectors(t->max_sectors,
+ t->logical_block_size);
+ t->max_hw_sectors = blk_round_down_sectors(t->max_hw_sectors,
+ t->logical_block_size);
+ t->max_dev_sectors = blk_round_down_sectors(t->max_dev_sectors,
+ t->logical_block_size);
+
+ return ret;
+}
+
/**
* blk_stack_limits - adjust queue_limits for stacked devices
* @t: the stacking driver limits (top device)
@@ -780,8 +869,8 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
sector_t start)
{
- unsigned int top, bottom, alignment;
- int ret = 0;
+ unsigned int alignment;
+ int ret;
t->features |= (b->features & BLK_FEAT_INHERIT_MASK);
@@ -798,8 +887,6 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
if (!(b->features & BLK_FEAT_PCI_P2PDMA))
t->features &= ~BLK_FEAT_PCI_P2PDMA;
- t->flags |= (b->flags & BLK_FLAG_MISALIGNED);
-
t->max_sectors = min_not_zero(t->max_sectors, b->max_sectors);
t->max_user_sectors = min_not_zero(t->max_user_sectors,
b->max_user_sectors);
@@ -830,80 +917,9 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
t->max_segment_size = min_not_zero(t->max_segment_size,
b->max_segment_size);
-
- alignment = queue_limit_alignment_offset(b, start);
-
- /* Bottom device has different alignment. Check that it is
- * compatible with the current top alignment.
- */
- if (t->alignment_offset != alignment) {
-
- top = max(t->physical_block_size, t->io_min)
- + t->alignment_offset;
- bottom = max(b->physical_block_size, b->io_min) + alignment;
-
- /* Verify that top and bottom intervals line up */
- if (max(top, bottom) % min(top, bottom)) {
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
- }
-
- t->logical_block_size = max(t->logical_block_size,
- b->logical_block_size);
-
- t->physical_block_size = max(t->physical_block_size,
- b->physical_block_size);
-
- t->io_min = max(t->io_min, b->io_min);
- t->io_opt = lcm_not_zero(t->io_opt, b->io_opt);
t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
- /* Set non-power-of-2 compatible chunk_sectors boundary */
- if (b->chunk_sectors)
- t->chunk_sectors = gcd(t->chunk_sectors, b->chunk_sectors);
-
- /* Physical block size a multiple of the logical block size? */
- if (t->physical_block_size & (t->logical_block_size - 1)) {
- t->physical_block_size = t->logical_block_size;
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
-
- /* Minimum I/O a multiple of the physical block size? */
- if (t->io_min & (t->physical_block_size - 1)) {
- t->io_min = t->physical_block_size;
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
-
- /* Optimal I/O a multiple of the physical block size? */
- if (t->io_opt & (t->physical_block_size - 1)) {
- t->io_opt = 0;
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
-
- /* chunk_sectors a multiple of the physical block size? */
- if (t->chunk_sectors % (t->physical_block_size >> SECTOR_SHIFT)) {
- t->chunk_sectors = 0;
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
-
- /* Find lowest common alignment_offset */
- t->alignment_offset = lcm_not_zero(t->alignment_offset, alignment)
- % max(t->physical_block_size, t->io_min);
-
- /* Verify that new alignment_offset is on a logical block boundary */
- if (t->alignment_offset & (t->logical_block_size - 1)) {
- t->flags |= BLK_FLAG_MISALIGNED;
- ret = -1;
- }
-
- t->max_sectors = blk_round_down_sectors(t->max_sectors, t->logical_block_size);
- t->max_hw_sectors = blk_round_down_sectors(t->max_hw_sectors, t->logical_block_size);
- t->max_dev_sectors = blk_round_down_sectors(t->max_dev_sectors, t->logical_block_size);
+ ret = blk_stack_topology_limits(t, b, start);
/* Discard alignment and granularity */
if (b->discard_granularity) {
--
2.25.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 3/5] block: factor path limits out of blk_stack_limits
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
2026-08-21 10:10 ` [PATCH v3 1/5] nvme: factor namespace-head queue-limit update Yao Sang
2026-08-21 10:10 ` [PATCH v3 2/5] block: factor mapped-range topology out of blk_stack_limits Yao Sang
@ 2026-08-21 10:10 ` Yao Sang
2026-08-21 10:10 ` [PATCH v3 4/5] block: factor atomic write hardware limit stacking Yao Sang
` (3 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
Same-LBA multipath needs to stack path limits without applying
mapped-range topology calculations.
Factor this path limit stacking into blk_stack_path_limits(). It covers
execution features, sector and segment limits, write zeroes, discard
segments, zone append and DMA alignment.
There is no behavior change for existing callers.
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
block/blk-settings.c | 77 +++++++++++++++++++++-----------------------
1 file changed, 37 insertions(+), 40 deletions(-)
diff --git a/block/blk-settings.c b/block/blk-settings.c
index 9e7cdaaefbca..f1a2e4fe4e77 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -756,6 +756,39 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
t->atomic_write_hw_boundary = 0;
}
+#define STACK_MIN_NOT_ZERO(t, b, field) \
+ ((t)->field = min_not_zero((t)->field, (b)->field))
+#define STACK_MIN(t, b, field) \
+ ((t)->field = min((t)->field, (b)->field))
+
+static void blk_stack_path_limits(struct queue_limits *t,
+ const struct queue_limits *b)
+{
+ /*
+ * These features must be supported by the top queue and every path that
+ * can execute I/O. Clear them when a path does not support them.
+ */
+ if (!(b->features & BLK_FEAT_NOWAIT))
+ t->features &= ~BLK_FEAT_NOWAIT;
+ if (!(b->features & BLK_FEAT_POLL))
+ t->features &= ~BLK_FEAT_POLL;
+ if (!(b->features & BLK_FEAT_PCI_P2PDMA))
+ t->features &= ~BLK_FEAT_PCI_P2PDMA;
+
+ STACK_MIN_NOT_ZERO(t, b, max_hw_sectors);
+ STACK_MIN_NOT_ZERO(t, b, max_dev_sectors);
+ STACK_MIN_NOT_ZERO(t, b, seg_boundary_mask);
+ STACK_MIN_NOT_ZERO(t, b, virt_boundary_mask);
+ STACK_MIN_NOT_ZERO(t, b, max_segments);
+ STACK_MIN_NOT_ZERO(t, b, max_integrity_segments);
+ STACK_MIN_NOT_ZERO(t, b, max_segment_size);
+ STACK_MIN(t, b, max_write_zeroes_sectors);
+ STACK_MIN(t, b, max_hw_wzeroes_unmap_sectors);
+ STACK_MIN_NOT_ZERO(t, b, max_discard_segments);
+ STACK_MIN(t, b, max_hw_zone_append_sectors);
+ t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
+}
+
/*
* Stack block sizes, I/O granularities, chunk boundaries and alignment for a
* bottom-device range mapped at @start. Round maximum sector limits after the
@@ -873,51 +906,14 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
int ret;
t->features |= (b->features & BLK_FEAT_INHERIT_MASK);
-
- /*
- * Some feaures need to be supported both by the stacking driver and all
- * underlying devices. The stacking driver sets these flags before
- * stacking the limits, and this will clear the flags if any of the
- * underlying devices does not support it.
- */
- if (!(b->features & BLK_FEAT_NOWAIT))
- t->features &= ~BLK_FEAT_NOWAIT;
- if (!(b->features & BLK_FEAT_POLL))
- t->features &= ~BLK_FEAT_POLL;
- if (!(b->features & BLK_FEAT_PCI_P2PDMA))
- t->features &= ~BLK_FEAT_PCI_P2PDMA;
+ blk_stack_path_limits(t, b);
t->max_sectors = min_not_zero(t->max_sectors, b->max_sectors);
t->max_user_sectors = min_not_zero(t->max_user_sectors,
b->max_user_sectors);
- t->max_hw_sectors = min_not_zero(t->max_hw_sectors, b->max_hw_sectors);
- t->max_dev_sectors = min_not_zero(t->max_dev_sectors, b->max_dev_sectors);
- t->max_write_zeroes_sectors = min(t->max_write_zeroes_sectors,
- b->max_write_zeroes_sectors);
t->max_user_wzeroes_unmap_sectors =
min(t->max_user_wzeroes_unmap_sectors,
b->max_user_wzeroes_unmap_sectors);
- t->max_hw_wzeroes_unmap_sectors =
- min(t->max_hw_wzeroes_unmap_sectors,
- b->max_hw_wzeroes_unmap_sectors);
-
- t->max_hw_zone_append_sectors = min(t->max_hw_zone_append_sectors,
- b->max_hw_zone_append_sectors);
-
- t->seg_boundary_mask = min_not_zero(t->seg_boundary_mask,
- b->seg_boundary_mask);
- t->virt_boundary_mask = min_not_zero(t->virt_boundary_mask,
- b->virt_boundary_mask);
-
- t->max_segments = min_not_zero(t->max_segments, b->max_segments);
- t->max_discard_segments = min_not_zero(t->max_discard_segments,
- b->max_discard_segments);
- t->max_integrity_segments = min_not_zero(t->max_integrity_segments,
- b->max_integrity_segments);
-
- t->max_segment_size = min_not_zero(t->max_segment_size,
- b->max_segment_size);
- t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
ret = blk_stack_topology_limits(t, b, start);
@@ -927,8 +923,9 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
t->max_discard_sectors = min_not_zero(t->max_discard_sectors,
b->max_discard_sectors);
- t->max_hw_discard_sectors = min_not_zero(t->max_hw_discard_sectors,
- b->max_hw_discard_sectors);
+ t->max_hw_discard_sectors =
+ min_not_zero(t->max_hw_discard_sectors,
+ b->max_hw_discard_sectors);
t->discard_granularity = max(t->discard_granularity,
b->discard_granularity);
t->discard_alignment = lcm_not_zero(t->discard_alignment, alignment) %
--
2.25.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 4/5] block: factor atomic write hardware limit stacking
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
` (2 preceding siblings ...)
2026-08-21 10:10 ` [PATCH v3 3/5] block: factor path limits " Yao Sang
@ 2026-08-21 10:10 ` Yao Sang
2026-08-21 10:10 ` [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers Yao Sang
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
blk_stack_atomic_writes_limits() stacks atomic write hardware limits and
checks whether @start meets the bottom-device alignment. Only the start
check depends on the top-to-bottom mapping.
Factor hardware limit stacking into blk_stack_atomic_writes_hw_limits()
so same-LBA multipath can stack atomic write limits without checking a
mapped start sector.
There is no behavior change for existing callers.
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
block/blk-settings.c | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/block/blk-settings.c b/block/blk-settings.c
index f1a2e4fe4e77..eaba38370657 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -653,6 +653,14 @@ static unsigned int blk_round_down_sectors(unsigned int sectors, unsigned int lb
return sectors;
}
+static void blk_clear_atomic_write_limits(struct queue_limits *lim)
+{
+ lim->atomic_write_hw_max = 0;
+ lim->atomic_write_hw_unit_max = 0;
+ lim->atomic_write_hw_unit_min = 0;
+ lim->atomic_write_hw_boundary = 0;
+}
+
/* Check if second and later bottom devices are compliant */
static bool blk_stack_atomic_writes_tail(struct queue_limits *t,
struct queue_limits *b)
@@ -726,8 +734,8 @@ static bool blk_stack_atomic_writes_head(struct queue_limits *t,
return true;
}
-static void blk_stack_atomic_writes_limits(struct queue_limits *t,
- struct queue_limits *b, sector_t start)
+static bool blk_stack_atomic_writes_hw_limits(struct queue_limits *t,
+ struct queue_limits *b)
{
if (!(b->features & BLK_FEAT_ATOMIC_WRITES))
goto unsupported;
@@ -735,9 +743,6 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
if (!b->atomic_write_hw_unit_min)
goto unsupported;
- if (!blk_atomic_write_start_sect_aligned(start, b))
- goto unsupported;
-
/* UINT_MAX indicates no stacking of bottom devices yet */
if (t->atomic_write_hw_max == UINT_MAX) {
if (!blk_stack_atomic_writes_head(t, b))
@@ -747,13 +752,19 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
goto unsupported;
}
blk_stack_atomic_writes_chunk_sectors(t);
- return;
+ return true;
unsupported:
- t->atomic_write_hw_max = 0;
- t->atomic_write_hw_unit_max = 0;
- t->atomic_write_hw_unit_min = 0;
- t->atomic_write_hw_boundary = 0;
+ blk_clear_atomic_write_limits(t);
+ return false;
+}
+
+static void blk_stack_atomic_writes_limits(struct queue_limits *t,
+ struct queue_limits *b, sector_t start)
+{
+ if (blk_stack_atomic_writes_hw_limits(t, b) &&
+ !blk_atomic_write_start_sect_aligned(start, b))
+ blk_clear_atomic_write_limits(t);
}
#define STACK_MIN_NOT_ZERO(t, b, field) \
--
2.25.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
` (3 preceding siblings ...)
2026-08-21 10:10 ` [PATCH v3 4/5] block: factor atomic write hardware limit stacking Yao Sang
@ 2026-08-21 10:10 ` Yao Sang
2026-09-07 9:44 ` John Garry
2026-08-31 8:03 ` [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
2026-09-07 9:34 ` John Garry
6 siblings, 1 reply; 10+ messages in thread
From: Yao Sang @ 2026-08-21 10:10 UTC (permalink / raw)
To: axboe, kbusch, hch, sagi; +Cc: john.g.garry, linux-block, linux-nvme, Yao Sang
A same-LBA multipath head and its paths address the same logical block
space, so they do not need mapped-range topology calculations.
Add blk_stack_mpath_limits() to stack limits that may differ between
paths, and blk_set_mpath_head_limits() to set head limits that are
expected to be identical across paths. Export both helpers and use them
for NVMe namespace heads.
Initialize max_hw_discard_sectors to UINT_MAX before stacking the first
path. A zero limit means that a path does not support discard, so it
disables discard for the head.
Signed-off-by: Yao Sang <sangyao@kylinos.cn>
---
block/blk-settings.c | 58 +++++++++++++++++++++++++++++++++++
drivers/nvme/host/core.c | 34 ++-------------------
drivers/nvme/host/multipath.c | 2 ++
include/linux/blkdev.h | 3 ++
4 files changed, 65 insertions(+), 32 deletions(-)
diff --git a/block/blk-settings.c b/block/blk-settings.c
index eaba38370657..8f70bab0a814 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -800,6 +800,64 @@ static void blk_stack_path_limits(struct queue_limits *t,
t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
}
+/**
+ * blk_set_mpath_head_limits - set head limits common to all paths
+ * @t: limits for the multipath head
+ * @b: limits for one path
+ *
+ * Set head limits that are expected to be identical across paths. Stack
+ * limits that may differ between paths with blk_stack_mpath_limits().
+ */
+void blk_set_mpath_head_limits(struct queue_limits *t,
+ struct queue_limits *b)
+{
+ t->logical_block_size = b->logical_block_size;
+ t->physical_block_size = b->physical_block_size;
+ t->alignment_offset = b->alignment_offset;
+ t->io_min = b->io_min;
+ t->io_opt = b->io_opt;
+ t->discard_granularity = b->discard_granularity;
+ t->discard_alignment = b->discard_alignment;
+ t->zone_write_granularity = b->zone_write_granularity;
+ t->max_write_streams = b->max_write_streams;
+ t->write_stream_granularity = b->write_stream_granularity;
+}
+EXPORT_SYMBOL_GPL(blk_set_mpath_head_limits);
+
+/**
+ * blk_stack_mpath_limits - stack limits across same-LBA multipath paths
+ * @t: limits for the multipath head
+ * @b: limits for one path
+ *
+ * Stack limits in @b that may differ between paths. Unlike
+ * blk_stack_limits(), this does not apply mapped-range topology or a mapping
+ * offset. Set limits that are expected to be identical across paths with
+ * blk_set_mpath_head_limits().
+ *
+ * Initialize @t with blk_set_stacking_limits() and set features that require
+ * support from every path before the first call. Set
+ * @t->max_hw_discard_sectors to UINT_MAX and call once for each path. A zero
+ * discard limit disables discard for the head.
+ */
+void blk_stack_mpath_limits(struct queue_limits *t, struct queue_limits *b)
+{
+ if (b->chunk_sectors)
+ t->chunk_sectors = gcd(t->chunk_sectors, b->chunk_sectors);
+
+ t->features |= b->features &
+ (BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA |
+ BLK_FEAT_ROTATIONAL | BLK_FEAT_STABLE_WRITES);
+ blk_stack_path_limits(t, b);
+ STACK_MIN(t, b, max_hw_discard_sectors);
+ blk_stack_atomic_writes_hw_limits(t, b);
+
+ if (t->features & BLK_FEAT_ZONED) {
+ STACK_MIN_NOT_ZERO(t, b, max_open_zones);
+ STACK_MIN_NOT_ZERO(t, b, max_active_zones);
+ }
+}
+EXPORT_SYMBOL_GPL(blk_stack_mpath_limits);
+
/*
* Stack block sizes, I/O granularities, chunk boundaries and alignment for a
* bottom-device range mapped at @start. Round maximum sector limits after the
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 8e2b44ed4366..a16986ec1c8e 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2530,14 +2530,6 @@ 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)
-{
- 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);
-}
-
static int nvme_update_ns_head_limits(struct nvme_ns *ns,
struct nvme_ns_info *info, bool unsupported)
{
@@ -2549,34 +2541,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 hardware limitations for bio splitting with 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);
+ blk_set_mpath_head_limits(&lim, ns_lim);
+ blk_stack_mpath_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;
diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
index 75dbb58286a3..448c7f33c687 100644
--- a/drivers/nvme/host/multipath.c
+++ b/drivers/nvme/host/multipath.c
@@ -760,6 +760,8 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head)
return 0;
blk_set_stacking_limits(&lim);
+ /* No path discard limit has been stacked yet. */
+ lim.max_hw_discard_sectors = UINT_MAX;
lim.dma_alignment = 3;
lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT |
BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES | BLK_FEAT_PCI_P2PDMA;
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 9213a5716f95..2fe711c196c9 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1153,6 +1153,9 @@ static inline void blk_queue_disable_write_zeroes(struct request_queue *q)
*/
extern void blk_set_queue_depth(struct request_queue *q, unsigned int depth);
extern void blk_set_stacking_limits(struct queue_limits *lim);
+void blk_set_mpath_head_limits(struct queue_limits *t,
+ struct queue_limits *b);
+void blk_stack_mpath_limits(struct queue_limits *t, struct queue_limits *b);
extern int blk_stack_limits(struct queue_limits *t, struct queue_limits *b,
sector_t offset);
void queue_limits_stack_bdev(struct queue_limits *t, struct block_device *bdev,
--
2.25.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
` (4 preceding siblings ...)
2026-08-21 10:10 ` [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers Yao Sang
@ 2026-08-31 8:03 ` Yao Sang
2026-09-07 9:34 ` John Garry
6 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-08-31 8:03 UTC (permalink / raw)
To: sangyao; +Cc: axboe, hch, john.g.garry, kbusch, linux-block, linux-nvme, sagi
Hi,
A gentle ping for this series. Any further comments would be appreciated.
Thanks,
Yao
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
` (5 preceding siblings ...)
2026-08-31 8:03 ` [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
@ 2026-09-07 9:34 ` John Garry
2026-09-10 10:08 ` Yao Sang
6 siblings, 1 reply; 10+ messages in thread
From: John Garry @ 2026-09-07 9:34 UTC (permalink / raw)
To: Yao Sang, axboe, kbusch, hch, sagi; +Cc: linux-block, linux-nvme, john.garry
On 21/08/2026 11:10, Yao Sang wrote:
> blk_stack_limits() stacks limits for a top-device range mapped onto a
> bottom device. Same-LBA multipath instead needs to stack limits across
> paths without applying mapped-range topology calculations.
Sorry, again, I'm finding this hard to follow.
My thoughts are that we should be doing the following:
- apply the "topology" limits from the first NS to the NS head
- those "topology" limits are fixed and like logical and physical block
size
- this is only ever done once for NS head lifetime
- then stack per-path (NS) limits, which may be affected by the
transport or HBA or controller
Am I right? If so, is this what this series is doing?
From checking nvme_update_ns_head_limits(), we seem to apply the
"topology" limit per NS (which I did not expect).
>
> Factor the common topology, path and atomic write code into helpers. Add
> block helpers for same-LBA multipath and use them for NVMe namespace heads.
> SCSI multipath can use the same helpers.
>
> Changes since v2:
> - replace the exported low-level helpers with block helpers for same-LBA
> multipath;
> - keep mapped-range and start-dependent checks inside blk_stack_limits().
>
> Review of v2 noted that fixed namespace limits should not be updated for
> each path. V3 separates these limits from path limit stacking, but keeps
> the existing NVMe update behavior.
>
> Changing this also requires explicit namespace-head revalidation after
> events such as Format NVM. That work is left for a follow-up.
>
> Tests:
> - 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.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers
2026-08-21 10:10 ` [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers Yao Sang
@ 2026-09-07 9:44 ` John Garry
0 siblings, 0 replies; 10+ messages in thread
From: John Garry @ 2026-09-07 9:44 UTC (permalink / raw)
To: Yao Sang, axboe, kbusch, hch, sagi; +Cc: linux-block, linux-nvme, john.garry
On 21/08/2026 11:10, Yao Sang wrote:
> A same-LBA multipath head and its paths address the same logical block
> space, so they do not need mapped-range topology calculations.
>
> Add blk_stack_mpath_limits() to stack limits that may differ between
> paths, and blk_set_mpath_head_limits() to set head limits that are
> expected to be identical across paths. Export both helpers and use them
> for NVMe namespace heads.
>
> Initialize max_hw_discard_sectors to UINT_MAX before stacking the first
> path. A zero limit means that a path does not support discard, so it
> disables discard for the head.
>
> Signed-off-by: Yao Sang<sangyao@kylinos.cn>
> ---
> block/blk-settings.c | 58 +++++++++++++++++++++++++++++++++++
> drivers/nvme/host/core.c | 34 ++-------------------
> drivers/nvme/host/multipath.c | 2 ++
> include/linux/blkdev.h | 3 ++
> 4 files changed, 65 insertions(+), 32 deletions(-)
>
> diff --git a/block/blk-settings.c b/block/blk-settings.c
> index eaba38370657..8f70bab0a814 100644
> --- a/block/blk-settings.c
> +++ b/block/blk-settings.c
> @@ -800,6 +800,64 @@ static void blk_stack_path_limits(struct queue_limits *t,
> t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
> }
>
> +/**
> + * blk_set_mpath_head_limits - set head limits common to all paths
> + * @t: limits for the multipath head
> + * @b: limits for one path
> + *
> + * Set head limits that are expected to be identical across paths. Stack
> + * limits that may differ between paths with blk_stack_mpath_limits().
> + */
> +void blk_set_mpath_head_limits(struct queue_limits *t,
> + struct queue_limits *b)
nit: mpath_head is a NVMe multipath term, so maybe we need something
more generic
> +{
> + t->logical_block_size = b->logical_block_size;
> + t->physical_block_size = b->physical_block_size;
> + t->alignment_offset = b->alignment_offset;
> + t->io_min = b->io_min;
> + t->io_opt = b->io_opt;
I would not say that io_opt is really fixed for the disk. For SCSI, we
change this value from transport/HBA factors - see sd_revalidate_disk()
and how io_opt may change from the SCSI HBA limits.
I think that you need to take a conservative approach here in deciding
what is a fixed limit for a disk.
> + t->discard_granularity = b->discard_granularity;
> + t->discard_alignment = b->discard_alignment;
> + t->zone_write_granularity = b->zone_write_granularity;
> + t->max_write_streams = b->max_write_streams;
> + t->write_stream_granularity = b->write_stream_granularity;
> +}
> +EXPORT_SYMBOL_GPL(blk_set_mpath_head_limits);
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking
2026-09-07 9:34 ` John Garry
@ 2026-09-10 10:08 ` Yao Sang
0 siblings, 0 replies; 10+ messages in thread
From: Yao Sang @ 2026-09-10 10:08 UTC (permalink / raw)
To: john.g.garry
Cc: axboe, hch, john.garry, kbusch, linux-block, linux-nvme, sagi,
sangyao
Hi John,
Sorry for the late reply, and thanks for the review.
Initializing the head limits only once needs some care, as we still
need to refresh them after Format NVM and namespace changes.
I'm working on this and will send v4 once it's ready.
Thanks,
Yao
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-10 10:09 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 10:10 [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
2026-08-21 10:10 ` [PATCH v3 1/5] nvme: factor namespace-head queue-limit update Yao Sang
2026-08-21 10:10 ` [PATCH v3 2/5] block: factor mapped-range topology out of blk_stack_limits Yao Sang
2026-08-21 10:10 ` [PATCH v3 3/5] block: factor path limits " Yao Sang
2026-08-21 10:10 ` [PATCH v3 4/5] block: factor atomic write hardware limit stacking Yao Sang
2026-08-21 10:10 ` [PATCH v3 5/5] block, nvme: add same-LBA multipath limit helpers Yao Sang
2026-09-07 9:44 ` John Garry
2026-08-31 8:03 ` [PATCH v3 0/5] block, nvme: support same-LBA multipath limit stacking Yao Sang
2026-09-07 9:34 ` John Garry
2026-09-10 10:08 ` Yao Sang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox