[PATCH v2 4/4] block, nvme: use lower-level limit helpers for namespace heads

Yao Sang <[email protected]> Thu, 6 Aug 2026 10:46:58 +0800
Newsgroups org.infradead.lists.linux-nvme,org.kernel.vger.linux-block
Message-ID <[email protected]>
An NVMe namespace head and each path refer to the same namespace.
queue_limits_stack_bdev() treats the path as a bottom-device range and
applies all of blk_stack_limits(), including topology rules and start-based
atomic write checks. These mapped-device rules should not be used when
updating the namespace head.

Make blk_stack_path_limits() and the existing
blk_stack_atomic_writes_limits() public when adding their first caller
outside the block layer. Keep the existing names and parameter types.

Refresh logical_block_size, physical_block_size, io_min, io_opt,
discard_granularity, zone_write_granularity and the write stream limits
from the namespace that was just scanned. Keep chunk_sectors conservative
for non-zoned namespaces because NVME_QUIRK_STRIPE_SIZE can make it
controller-specific, while zoned namespaces refresh the zone size directly.

Use blk_stack_path_limits() for the all-path execution feature bits and
hardware limits that must work for every path, and use
blk_stack_atomic_writes_limits() with a zero start sector. Keep the
non-path feature handling in NVMe: the namespace head needs only inherited
features that can describe an NVMe path. Zoned is handled with the
namespace layout, and RAID partial stripes are not an NVMe namespace-head
property.

Keep max_user_* from queue_limits_start_update(), rebuild the integrity
profile with nvme_init_integrity(), and retain the existing write zeroes,
discard and zone resource updates in NVMe.

Signed-off-by: Yao Sang <[email protected]>
---
 block/blk-settings.c     |  23 +++++++-
 drivers/nvme/host/core.c | 115 +++++++++++++++++++++++++++++----------
 include/linux/blkdev.h   |   4 ++
 3 files changed, 109 insertions(+), 33 deletions(-)

diff --git a/block/blk-settings.c b/block/blk-settings.c
index 40cd3490f168..877834e88745 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -726,8 +726,14 @@ 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)
+/**
+ * blk_stack_atomic_writes_limits - stack atomic write limits
+ * @t: the stacking driver limits (top device)
+ * @b: the underlying queue limits (bottom device)
+ * @start: first data sector within bottom device
+ */
+void blk_stack_atomic_writes_limits(struct queue_limits *t,
+		struct queue_limits *b, sector_t start)
 {
 	if (!(b->features & BLK_FEAT_ATOMIC_WRITES))
 		goto unsupported;
@@ -755,8 +761,18 @@ static void blk_stack_atomic_writes_limits(struct queue_limits *t,
 	t->atomic_write_hw_unit_min = 0;
 	t->atomic_write_hw_boundary = 0;
 }
+EXPORT_SYMBOL_GPL(blk_stack_atomic_writes_limits);
 
-static void blk_stack_path_limits(struct queue_limits *t,
+/**
+ * blk_stack_path_limits - update limits that must hold for every I/O path
+ * @t: the queue limits to update
+ * @b: the queue limits for an I/O path
+ *
+ * Clear BLK_FEAT_NOWAIT, BLK_FEAT_POLL and BLK_FEAT_PCI_P2PDMA when they
+ * are not set in @b. Stack the sector, segment, integrity segment and DMA
+ * alignment limits that every path must support.
+ */
+void blk_stack_path_limits(struct queue_limits *t,
 		const struct queue_limits *b)
 {
 	/*
@@ -785,6 +801,7 @@ static void blk_stack_path_limits(struct queue_limits *t,
 					   b->max_segment_size);
 	t->dma_alignment = max(t->dma_alignment, b->dma_alignment);
 }
+EXPORT_SYMBOL_GPL(blk_stack_path_limits);
 
 /*
  * Stack and check logical_block_size, physical_block_size, io_min, io_opt,
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index e3d27c0440db..181394380510 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -11,6 +11,7 @@
 #include <linux/compat.h>
 #include <linux/delay.h>
 #include <linux/errno.h>
+#include <linux/gcd.h>
 #include <linux/hdreg.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -2520,12 +2521,89 @@ 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);
+	/*
+	 * The namespace scan sets these values from Identify data and limits
+	 * calculated from it. Refresh them instead of combining them with values
+	 * left from an earlier namespace scan.
+	 */
+	lim->features &= ~BLK_FEAT_ZONED;
+	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;
+	/* A non-zoned path may have a controller-specific stripe size. */
+	if (ns_lim->features & BLK_FEAT_ZONED)
+		lim->chunk_sectors = ns_lim->chunk_sectors;
+	else if (ns_lim->chunk_sectors)
+		lim->chunk_sectors = gcd(lim->chunk_sectors,
+					 ns_lim->chunk_sectors);
+	lim->alignment_offset = 0;
+	lim->discard_alignment = 0;
+	lim->flags &= ~BLK_FLAG_MISALIGNED;
+	lim->discard_granularity = ns_lim->discard_granularity;
+	lim->zone_write_granularity = ns_lim->zone_write_granularity;
+	lim->max_write_streams = ns_lim->max_write_streams;
+	lim->write_stream_granularity = ns_lim->write_stream_granularity;
+}
+
+static void nvme_apply_ns_head_operation_limits(struct queue_limits *lim,
+		const struct queue_limits *ns_lim)
+{
+	/* Keep the existing minimums for controller command limits. */
+	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->max_discard_segments =
+		min_not_zero(lim->max_discard_segments,
+			     ns_lim->max_discard_segments);
+	if (ns_lim->discard_granularity)
+		lim->max_hw_discard_sectors =
+			min_not_zero(lim->max_hw_discard_sectors,
+				     ns_lim->max_hw_discard_sectors);
+	blk_stack_atomic_writes_limits(lim, ns_lim, 0);
+}
+
+static void nvme_apply_ns_head_zone_limits(struct queue_limits *lim,
+		const struct queue_limits *ns_lim)
+{
+	/*
+	 * Zone geometry was set above. max_hw_zone_append_sectors must work
+	 * for every path, while max_open_zones and max_active_zones are
+	 * namespace resources.
+	 */
+	lim->max_hw_zone_append_sectors =
+		min(lim->max_hw_zone_append_sectors,
+		    ns_lim->max_hw_zone_append_sectors);
+	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);
+}
+
+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);
+	/*
+	 * Keep inherited non-path features that can describe an NVMe path.
+	 * Zoned was handled as namespace layout above, and the RAID partial
+	 * stripes flag is not an NVMe namespace-head property.
+	 */
+	lim->features |= ns_lim->features &
+		(BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA |
+		 BLK_FEAT_ROTATIONAL | BLK_FEAT_STABLE_WRITES);
+	blk_stack_path_limits(lim, ns_lim);
+	nvme_apply_ns_head_operation_limits(lim, ns_lim);
+	if (lim->features & BLK_FEAT_ZONED)
+		nvme_apply_ns_head_zone_limits(lim, ns_lim);
 }
 
 static int nvme_update_ns_head_limits(struct nvme_ns *ns,
@@ -2539,35 +2617,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;
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 9213a5716f95..b89930f1f7a6 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1153,6 +1153,10 @@ 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_stack_path_limits(struct queue_limits *t,
+		const struct queue_limits *b);
+void blk_stack_atomic_writes_limits(struct queue_limits *t,
+		struct queue_limits *b, sector_t start);
 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