From: Aaron Tomlin <atomlin@atomlin.com>
To: axboe@kernel.dk, tglx@kernel.org, aacraid@microsemi.com,
James.Bottomley@HansenPartnership.com, mkp@kernel.org,
frederic@kernel.org, bigeasy@linutronix.de
Cc: atomlin@atomlin.com, ionut.nechita@windriver.com, corbet@lwn.net,
vincent.guittot@linaro.org, mingo@redhat.com,
peterz@infradead.org, radu@rendec.net, akpm@linux-foundation.org,
steve@abita.co, sean@ashe.io, chjohnst@gmail.com, neelx@suse.com,
mproche@gmail.com, nick.lange@gmail.com,
marco.crivellari@suse.com, rishil1999@outlook.com,
linux-doc@vger.kernel.org, linux-block@vger.kernel.org,
linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v16 6/9] blk-mq: use hk cpus only when isolcpus=managed_irq_strict is enabled
Date: Thu, 10 Sep 2026 12:42:34 -0400 [thread overview]
Message-ID: <20260910164237.500196-7-atomlin@atomlin.com> (raw)
In-Reply-To: <20260910164237.500196-1-atomlin@atomlin.com>
From: Daniel Wagner <wagi@kernel.org>
Extend the capabilities of the generic CPU to hardware queue (hctx)
mapping code, so it maps housekeeping CPUs and isolated CPUs to the
hardware queues evenly.
Example mapping result:
16 online CPUs
isolcpus=managed_irq_strict,2-3,6-7,12-13
Queue mapping:
hctx0: default 0 2
hctx1: default 1 3
hctx2: default 4 6
hctx3: default 5 7
hctx4: default 8 12
hctx5: default 9 13
hctx6: default 10
hctx7: default 11
hctx8: default 14
hctx9: default 15
IRQ mapping:
irq 42 affinity 0 effective 0 nvme0q0
irq 43 affinity 0 effective 0 nvme0q1
irq 44 affinity 1 effective 1 nvme0q2
irq 45 affinity 4 effective 4 nvme0q3
irq 46 affinity 5 effective 5 nvme0q4
irq 47 affinity 8 effective 8 nvme0q5
irq 48 affinity 9 effective 9 nvme0q6
irq 49 affinity 10 effective 10 nvme0q7
irq 50 affinity 11 effective 11 nvme0q8
irq 51 affinity 14 effective 14 nvme0q9
irq 52 affinity 15 effective 15 nvme0q10
In this scenario, the system has 16 online CPUs with 6 isolated cores
(2, 3, 6, 7, 12, and 13) and 10 housekeeping cores (0, 1, 4, 5, 8, 9,
10, 11, 14, and 15):
1. Queue allocation and ownership
Rather than allocating 16 hardware queues, the block layer
allocates only 10 hardware contexts (hctx0 to hctx9),
corresponding strictly to the 10 housekeeping CPUs. The 6
isolated CPUs do not own dedicated hardware queues; instead,
they are mapped across the existing active housekeeping queues
(e.g. isolated CPU 2 shares hctx0 with CPU 0). This ensures
tasks running on isolated CPUs can still issue I/O without
restriction.
2. Interrupt routing
All device interrupts, including the NVMe admin queue (irq 42)
and the 10 I/O completion queues (irq 43 to 52), target
housekeeping CPUs exclusively. When a task on isolated CPU 2
issues I/O via hctx0, the resulting completion interrupt (irq
43) fires on housekeeping CPU 0. Consequently, isolated CPUs are
never interrupted by device hardware, guaranteeing zero latency
disturbance for isolated workloads.
A corner case is when the number of online CPUs and present CPUs
differ and the driver asks for less queues than online CPUs, e.g.
8 online CPUs, 16 possible CPUs
isolcpus=managed_irq_strict,2-3,6-7,12-13
virtio_blk.num_request_queues=2
Queue mapping:
hctx0: default 0 1 2 3 4 5 6 7 8 12 13
hctx1: default 9 10 11 14 15
IRQ mapping
irq 27 affinity 0 effective 0 virtio0-config
irq 28 affinity 0-1,4-5,8 effective 5 virtio0-req.0
irq 29 affinity 9-11,14-15 effective 0 virtio0-req.1
This corner case demonstrates behaviour when hardware queue counts are
constrained (only 2 request queues) on a system with CPU hotplug (8
CPUs online out of 16 possible):
1. Coarse queue grouping
Because the driver requests only 2 queues, the 10 possible
housekeeping CPUs are partitioned into two groups: hctx0
receives CPUs 0-1, 4-5, and 8, while hctx1 receives CPUs 9-11
and 14-15. All isolated CPUs (both online cores 2-3, 6-7 and
offline cores 12-13) are mapped to hctx0 to share submission
capacity without allocating excess queues.
2. Isolation and hotplug protection in interrupt affinity
Although hctx0 serves both housekeeping and isolated CPUs, the
resulting interrupt affinity mask for irq 28 (affinity
0-1,4-5,8) strictly includes only the housekeeping cores,
completely excluding isolated cores 2-3, 6-7, and 12-13.
Furthermore, for hctx1 (irq 29), whose assigned housekeeping
CPUs (9-11, 14-15) are currently offline, the kernel routes the
effective interrupt to an available online housekeeping core
(CPU 0), guaranteeing that interrupts never spill onto isolated
cores under any hotplug state.
Noteworthy is that for the normal/default configuration (without
isolcpus=) the mapping will change for systems which have non
hyperthreading CPUs. The main assignment loop will completely rely that
group_mask_cpus_evenly to do the right thing. The old code would
distribute the CPUs linearly over the hardware context:
queue mapping for /dev/nvme0n1
hctx0: default 0 8
hctx1: default 1 9
hctx2: default 2 10
hctx3: default 3 11
hctx4: default 4 12
hctx5: default 5 13
hctx6: default 6 14
hctx7: default 7 15
The assign each hardware context the map generated by the
group_mask_cpus_evenly function:
queue mapping for /dev/nvme0n1
hctx0: default 0 1
hctx1: default 2 3
hctx2: default 4 5
hctx3: default 6 7
hctx4: default 8 9
hctx5: default 10 11
hctx6: default 12 13
hctx7: default 14 15
In case of hyperthreading CPUs, the resulting map stays the same.
Signed-off-by: Daniel Wagner <wagi@kernel.org>
Co-developed-by: Aaron Tomlin <atomlin@atomlin.com>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
block/blk-mq-cpumap.c | 163 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 145 insertions(+), 18 deletions(-)
diff --git a/block/blk-mq-cpumap.c b/block/blk-mq-cpumap.c
index 705da074ad6c..cec5b26bd57c 100644
--- a/block/blk-mq-cpumap.c
+++ b/block/blk-mq-cpumap.c
@@ -22,8 +22,15 @@ static unsigned int blk_mq_num_queues(const struct cpumask *mask,
{
unsigned int num;
- num = cpumask_weight(mask);
- return min_not_zero(num, max_queues);
+ if (housekeeping_enabled(HK_TYPE_MANAGED_IRQ_STRICT))
+ num = cpumask_weight_and(mask, housekeeping_cpumask(HK_TYPE_MANAGED_IRQ_STRICT));
+ else
+ num = cpumask_weight(mask);
+ /*
+ * Ensure that a count of zero does not inadvertently result in
+ * allocating the maximum number of queues.
+ */
+ return min_not_zero(num ?: 1U, max_queues);
}
/**
@@ -33,7 +40,8 @@ static unsigned int blk_mq_num_queues(const struct cpumask *mask,
* ignored.
*
* Calculates the number of queues to be used for a multiqueue
- * device based on the number of possible CPUs.
+ * device based on the number of possible CPUs. This helper
+ * takes isolcpus settings into account.
*/
unsigned int blk_mq_num_possible_queues(unsigned int max_queues)
{
@@ -48,7 +56,8 @@ EXPORT_SYMBOL_GPL(blk_mq_num_possible_queues);
* ignored.
*
* Calculates the number of queues to be used for a multiqueue
- * device based on the number of online CPUs.
+ * device based on the number of online CPUs. This helper
+ * takes isolcpus settings into account.
*/
unsigned int blk_mq_num_online_queues(unsigned int max_queues)
{
@@ -56,23 +65,81 @@ unsigned int blk_mq_num_online_queues(unsigned int max_queues)
}
EXPORT_SYMBOL_GPL(blk_mq_num_online_queues);
+static void blk_mq_map_fallback(struct blk_mq_queue_map *qmap)
+{
+ unsigned int cpu;
+
+ /*
+ * Map all CPUs to the first hctx of this specific map, respecting
+ * the map's boundaries so secondary maps do not route into the default map.
+ */
+ for_each_possible_cpu(cpu)
+ qmap->mq_map[cpu] = qmap->queue_offset;
+}
+
void blk_mq_map_queues(struct blk_mq_queue_map *qmap)
{
- const struct cpumask *masks;
+ struct cpumask *masks;
+ const struct cpumask *constraint;
unsigned int queue, cpu, nr_masks;
+ unsigned long *active_hctx;
- masks = group_cpus_evenly(qmap->nr_queues, &nr_masks);
- if (!masks) {
- for_each_possible_cpu(cpu)
- qmap->mq_map[cpu] = qmap->queue_offset;
- return;
- }
+ active_hctx = bitmap_zalloc(qmap->nr_queues, GFP_KERNEL);
+ if (!active_hctx)
+ goto fallback;
- for (queue = 0; queue < qmap->nr_queues; queue++) {
- for_each_cpu(cpu, &masks[queue % nr_masks])
+ if (housekeeping_enabled(HK_TYPE_MANAGED_IRQ_STRICT))
+ constraint = housekeeping_cpumask(HK_TYPE_MANAGED_IRQ_STRICT);
+ else
+ constraint = cpu_possible_mask;
+
+ /* Map CPUs to the hardware contexts (hctx) */
+ masks = group_mask_cpus_evenly(qmap->nr_queues, constraint, &nr_masks);
+ if (!masks)
+ goto free_fallback_hctx;
+
+ /*
+ * Iterate directly over the generated CPU masks.
+ * Calculate the final, highest hardware queue index that maps to this
+ * mask. This skips all intermediate overwrites and safely evaluates
+ * active_hctx only for queues that survive the mapping.
+ */
+ for (unsigned int idx = 0; idx < nr_masks; idx++) {
+ queue = qmap->nr_queues - 1 -
+ ((qmap->nr_queues - 1 - idx) % nr_masks);
+
+ for_each_cpu(cpu, &masks[idx])
qmap->mq_map[cpu] = qmap->queue_offset + queue;
+
+ __set_bit(queue, active_hctx);
+ }
+
+ /*
+ * If the active_hctx bitmap is empty, attempting to route unassigned
+ * CPUs will map them out-of-bounds. Fall back instead.
+ */
+ if (bitmap_empty(active_hctx, qmap->nr_queues))
+ goto free_fallback;
+
+ /* Map any unassigned CPU evenly to the hardware contexts (hctx) */
+ queue = find_first_bit(active_hctx, qmap->nr_queues);
+ for_each_cpu_andnot(cpu, cpu_possible_mask, constraint) {
+ qmap->mq_map[cpu] = qmap->queue_offset + queue;
+ queue = find_next_bit_wrap(active_hctx, qmap->nr_queues, queue + 1);
}
+
+ kfree(masks);
+ bitmap_free(active_hctx);
+
+ return;
+
+free_fallback:
kfree(masks);
+free_fallback_hctx:
+ bitmap_free(active_hctx);
+
+fallback:
+ blk_mq_map_fallback(qmap);
}
EXPORT_SYMBOL_GPL(blk_mq_map_queues);
@@ -109,24 +176,84 @@ void blk_mq_map_hw_queues(struct blk_mq_queue_map *qmap,
struct device *dev, unsigned int offset)
{
- const struct cpumask *mask;
+ cpumask_var_t mask;
+ const struct cpumask *constraint;
+ unsigned long *active_hctx;
unsigned int queue, cpu;
if (!dev->bus->irq_get_affinity)
+ goto map_software;
+
+ active_hctx = bitmap_zalloc(qmap->nr_queues, GFP_KERNEL);
+ if (!active_hctx)
goto fallback;
+ if (!zalloc_cpumask_var(&mask, GFP_KERNEL)) {
+ bitmap_free(active_hctx);
+ goto fallback;
+ }
+
+ if (housekeeping_enabled(HK_TYPE_MANAGED_IRQ_STRICT))
+ constraint = housekeeping_cpumask(HK_TYPE_MANAGED_IRQ_STRICT);
+ else
+ constraint = cpu_possible_mask;
+
+ /* Map CPUs to the hardware contexts (hctx) */
for (queue = 0; queue < qmap->nr_queues; queue++) {
- mask = dev->bus->irq_get_affinity(dev, queue + offset);
- if (!mask)
- goto fallback;
+ const struct cpumask *affinity_mask;
+
+ affinity_mask = dev->bus->irq_get_affinity(dev, offset + queue);
+ if (!affinity_mask)
+ goto free_map_software;
- for_each_cpu(cpu, mask)
+ for_each_cpu(cpu, affinity_mask) {
qmap->mq_map[cpu] = qmap->queue_offset + queue;
+ cpumask_set_cpu(cpu, mask);
+ }
}
+ /*
+ * Evaluate active_hctx after mapping to handle overlapping masks.
+ * This ensures queues that were overwritten do not falsely pass validation.
+ */
+ for_each_cpu(cpu, mask) {
+ if (cpumask_test_cpu(cpu, constraint)) {
+ queue = qmap->mq_map[cpu] - qmap->queue_offset;
+ __set_bit(queue, active_hctx);
+ }
+ }
+
+ /*
+ * If no assigned CPU matches the constraint, the active_hctx
+ * bitmap will be empty. Fall back instead of routing out of bounds.
+ */
+ if (bitmap_empty(active_hctx, qmap->nr_queues))
+ goto free_fallback;
+
+ /* Map any unassigned CPU evenly to the hardware contexts (hctx) */
+ queue = find_first_bit(active_hctx, qmap->nr_queues);
+ for_each_cpu_andnot(cpu, cpu_possible_mask, mask) {
+ qmap->mq_map[cpu] = qmap->queue_offset + queue;
+ queue = find_next_bit_wrap(active_hctx, qmap->nr_queues, queue + 1);
+ }
+
+ bitmap_free(active_hctx);
+ free_cpumask_var(mask);
+
return;
+free_fallback:
+ bitmap_free(active_hctx);
+ free_cpumask_var(mask);
+
fallback:
+ blk_mq_map_fallback(qmap);
+ return;
+
+free_map_software:
+ free_cpumask_var(mask);
+ bitmap_free(active_hctx);
+map_software:
blk_mq_map_queues(qmap);
}
EXPORT_SYMBOL_GPL(blk_mq_map_hw_queues);
--
2.55.0
next prev parent reply other threads:[~2026-09-10 16:43 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 16:42 [PATCH v16 0/9] blk: honor isolcpus configuration Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 1/9] scsi: aacraid: use block layer helpers to calculate num of queues Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 2/9] lib/group_cpus: remove dead !SMP code Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 3/9] lib/group_cpus: Add group_mask_cpus_evenly() Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 4/9] sched/isolation: Prevent out-of-bounds read in isolcpus= boot parameter parser Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 5/9] isolation: Introduce managed_irq_strict isolcpus type Aaron Tomlin
2026-09-10 16:42 ` Aaron Tomlin [this message]
2026-09-10 16:42 ` [PATCH v16 7/9] blk-mq: prevent offlining hk CPUs with associated online isolated CPUs Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 8/9] genirq/affinity: Restrict managed IRQ affinity to housekeeping CPUs Aaron Tomlin
2026-09-10 16:42 ` [PATCH v16 9/9] docs: add managed_irq_strict flag to isolcpus Aaron Tomlin
2026-09-10 18:26 ` [PATCH v16 0/9] blk: honor isolcpus configuration Aaron Tomlin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910164237.500196-7-atomlin@atomlin.com \
--to=atomlin@atomlin.com \
--cc=James.Bottomley@HansenPartnership.com \
--cc=aacraid@microsemi.com \
--cc=akpm@linux-foundation.org \
--cc=axboe@kernel.dk \
--cc=bigeasy@linutronix.de \
--cc=chjohnst@gmail.com \
--cc=corbet@lwn.net \
--cc=frederic@kernel.org \
--cc=ionut.nechita@windriver.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=marco.crivellari@suse.com \
--cc=mingo@redhat.com \
--cc=mkp@kernel.org \
--cc=mproche@gmail.com \
--cc=neelx@suse.com \
--cc=nick.lange@gmail.com \
--cc=peterz@infradead.org \
--cc=radu@rendec.net \
--cc=rishil1999@outlook.com \
--cc=sean@ashe.io \
--cc=steve@abita.co \
--cc=tglx@kernel.org \
--cc=vincent.guittot@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®