[RFC PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads
Yao Sang
sangyao at kylinos.cn
Thu Jul 9 01:48:25 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 an NVMe-local helper that copies refreshed namespace configuration,
keeps head user caps from queue_limits_start_update(), preserves
the existing write-cache and compatibility feature inheritance, and merges
request limits conservatively. Head-level execution features are kept only
when every usable path supports them.
Keep the helper local to NVMe for now so field ownership can be reviewed
before any block core helper is considered.
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 b96fa993a08f..d5e45dc13041 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2509,12 +2509,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,
@@ -2528,35 +2633,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);
set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk));
--
2.25.1
More information about the Linux-nvme
mailing list