[PATCH v1 2/2] nvme: stop using queue_limits_stack_bdev for namespace heads
Yao Sang
sangyao at kylinos.cn
Tue Jul 21 05:57:35 PDT 2026
On Mon, Jul 20, 2026 at 04:46:49PM +0200, Christoph Hellwig wrote:
> 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?
Sorry, that wording was unclear. This means that the common fields continue
to use the existing block-layer merge rules as paths are refreshed
> > Keep the helper local to NVMe so its field ownership remains explicit and
>
> What is "field ownership" supposed to mean?
What I meant was grouping fields by how they are updated: Identify-derived,
head-owned, feature, and path fields.
> > 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.
The key difference is that NVMe paths address the same LBA space, so they do
not need component offsets, mapped alignment, or generic topology merging.
> > +static void nvme_apply_ns_head_identify_limits(struct queue_limits *lim,
> > + const struct queue_limits *ns_lim)
>
> Two tab indents, please.
Yes, I'll fix the indentation.
> > {
> > - 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?
The initial NVMe-local change was meant to limit block-layer changes and
preserve the established blk_stack_limits() behavior for existing callers
while the common field semantics were unclear.
I agree that the common rules should not be duplicated. I understand "the
core code" to mean the block-layer stacking code in blk_stack_limits(). Based
on that, I would first add a behavior-preserving block-layer refactoring and
factor out the fields that have identical composition rules for generic
stacking and same-LBA paths. In pseudo-code, the helper and its call sites are:
/*
* Combine constraints required by every underlying queue that may execute
* an I/O, independent of mapping, topology, operation, user or resource
* policy.
*/
void blk_stack_io_limits(struct queue_limits *t,
const struct queue_limits *b)
{
/* clear unsupported NOWAIT/POLL/PCI_P2PDMA bits */
/*
* Apply min_not_zero(t->field, b->field) to:
* max_hw_sectors, max_dev_sectors,
* seg_boundary_mask, virt_boundary_mask,
* max_segments, max_integrity_segments, max_segment_size.
*/
/* apply max(t->field, b->field) to dma_alignment */
}
blk_stack_limits(t, b, start)
{
/* existing inherited-feature handling */
blk_stack_io_limits(t, b);
/* all remaining existing code stays in its current order */
}
nvme_apply_ns_head_limits(lim, ns_lim)
{
/* existing Identify/head and inherited-feature handling */
blk_stack_io_limits(lim, ns_lim);
/* all remaining NVMe-local code stays in its current order */
}
Existing DM, MD/RAID, DRBD and Btrfs callers keep using blk_stack_limits()
without a behavior change, while NVMe calls the lower-level helper directly.
The key distinction is the composition relationship: NVMe native paths
address the same LBA space, while generic stacking may remap LBAs and compose
topology. This version therefore factors out only the mapping-independent
rules shared by both cases into blk_stack_io_limits(), and leaves the remaining
handling in its existing paths. I would split further only when another common
composition rule is identified.
Does this match the block-layer boundary you had in mind, or should this
series factor out additional composition rules from blk_stack_limits()?
Thanks,
Yao
More information about the Linux-nvme
mailing list