[PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads
Yao Sang
sangyao at kylinos.cn
Sun Jul 19 19:22:05 PDT 2026
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 at 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
More information about the Linux-nvme
mailing list