Repository navigation
irqchip/apple-aic: Report the effective affinity of AICv2+ IRQs - #228
Conversation
[Medium impact] The PR appears safe to merge, with the previous hotplug finding addressed and no new blocking issues found. SummaryThe PR reports the CPUs that receive hardware IRQs on T8140 at EL2, allowing IRQ threads to run on those CPUs.
Reviews (2) · Last reviewed commit: "irqchip/apple-aic: Report the effective ..." · Reviewed by Greptile |
4542a65 to
4e2f3fa
Compare
maralcbr
left a comment
There was a problem hiding this comment.
Thanks @DjDeveloperr, this is a nice find. The before/after irq/65 placement and the gpubench numbers make a convincing case for T8140. No blockers in the code.
Checked and fine:
- The gate is in the right place. Only
apple,t8140-aic3setssiq_cfg_routing(irq-apple-aic.c:322-328).t8140.dtsi:677lists that compatible first, so it wins the match. The register is read only at EL2 (:1010). - The reported set no longer depends on which CPUs are online (
:502-504), so a CPU onlined late can't narrow it. On the other AICv2+ SoCs, threads getcpu_possible_mask, which is where they sit on the base anyway (kernel/irq/manage.c:1432). - The commit carries a
Signed-off-bynow.check-commits.pypasses locally, and checkpatch is clean on the diff.
Smaller things, none blocking:
- The other AICv2+ SoCs aren't quite unchanged.
aic2_chipnow hasirq_set_affinityeverywhere (:550). On M1 Pro/Max, M2 and M3, a write to/proc/irq/N/smp_affinitynow succeeds and is ignored, where it used to fail with-EPERM, andsmp_affinitythen shows whatever was written. PCIe MSI affinity calls now reach AIC as well and rewrite the same MSI message. I don't see harm in any of that, and AICv1 already takes the MSI path. If you'd rather keep those SoCs exactly as they are, a second chip thataic_irq_domain_map(:728-729) picks only whensiq_cfg_routing && is_kernel_in_hyp_mode()would do it. Otherwise, please list these changes in the PR body. - Please note the T8140 behaviour change in the PR body. At EL2, every ordinary AIC IRQ thread now runs on CPU 0, PCIe MSI ones included, and
smp_affinitywrites don't move it. Your CPU 0 load numbers cover that for gpubench. - nit: the comment at
:497-500says the set doesn't depend on which CPUs are online. On the SIQ_CFG path, though, bits are added as each CPU starts (:1012-1018), and existing IRQs aren't refreshed. That's fine while only CPU 0 accepts IRQs. A line bysiq_cfg_routing(:275) would help whoever adds the next SoC: one with more than one accepting CPU would need a refresh when a CPU comes online. - nit: the PR body still says checkpatch is clean "apart from Signed-off-by, which this repo doesn't use". The repo does require it (
tools/aurora-ci/README.rst:52-54), and the commit now has one, so that line can go.
CI was still running when I looked. Only plan and the CI self-test had passed, so commits, compile, device trees, host tests, package and KUnit hadn't finished on this head. I'll approve once they're green.
For release (not the source merge): the tested boots used the ungated 7.3 build. Please post the Neo boot of this 7.2.11 build, showing /proc/irq/65/effective_affinity_list and where irq/65 runs. CI's boot test is QEMU-only, and the chip change reaches every AICv2+ SoC. So unless it moves to a separate chip, I'll also boot it on our M2 Max with Wi-Fi up before release.
| .irq_mask = aic_irq_mask, | ||
| .irq_unmask = aic_irq_unmask, | ||
| .irq_eoi = aic_irq_eoi, | ||
| .irq_set_affinity = aic_irq_set_affinity, |
There was a problem hiding this comment.
This reaches every AICv2+ SoC. On those, smp_affinity writes now succeed and are ignored, and MSI affinity calls reach AIC. I'm fine with that if it's listed in the PR body. A separate chip, picked only when siq_cfg_routing && is_kernel_in_hyp_mode(), would keep the other SoCs unchanged.
There was a problem hiding this comment.
Moved to a separate chip in 1a10581: aic_irq_domain_map() picks aic2_routed_chip only when siq_cfg_routing && is_kernel_in_hyp_mode(). Every other SoC maps to the unchanged aic2_chip, so smp_affinity writes still fail there and MSI affinity calls don't reach AIC. aic_irq_set_affinity() is back to its original AICv1-only form.
| * Report those CPUs, so that threaded handlers run where the | ||
| * hard IRQ is taken; where SIQ_CFG was not read, any CPU may | ||
| * take it. The set does not depend on which CPUs are online, | ||
| * so CPU hotplug never narrows it. |
There was a problem hiding this comment.
nit: on the SIQ_CFG path the set grows as CPUs start (:1012-1018), and existing IRQs aren't refreshed. So "does not depend on which CPUs are online" only holds while just CPU 0 accepts IRQs.
There was a problem hiding this comment.
Right. The comment no longer makes that claim. The routed chip's comment says what the callback does, and the siq_cfg_routing field comment says the set grows as CPUs start while existing IRQs keep theirs, which is enough only while one CPU accepts IRQs.
AICv2 and later have no per-IRQ CPU target. The controller hands each hardware IRQ to one of the CPUs that accept IRQs, and each CPU says whether it does in its SIQ_CFG system register: a CPU whose routing field (bits 1:0) reads 3 never receives one. On T8140 the boot CPU reads 0 and every secondary reads 3, so every hardware IRQ is taken on CPU 0, even while CPU 0 holds IRQs masked for tens of milliseconds. Setting a secondary to 1 or 2 makes the controller deliver to it as well. The AICv2 irqchip has no irq_set_affinity, so the effective affinity of its IRQs stays empty and their threaded handlers may run on any CPU. The scheduler often places them away from the CPU taking the hard IRQ, which adds a cross-CPU wakeup to every interrupt. On SoCs where this routing has been verified on hardware, currently T8140, and only at EL2, since a hypervisor or secure monitor may trap SIQ_CFG, map AICv2+ IRQs to a second irqchip. It records at CPU bring-up which CPUs accept IRQs and reports them as each IRQ's effective affinity, so that genirq binds threaded handlers to the CPUs the hard IRQ arrives on. The requested mask cannot change delivery; it is accepted and kept only for reporting. On T8140 every AIC IRQ thread, PCIe MSI ones included, therefore runs on CPU 0, and smp_affinity writes do not move it. Every other SoC keeps the existing irqchip unchanged. The accepting set grows as CPUs start and existing IRQs keep theirs, which is enough while only one CPU accepts IRQs. A SoC with more than one accepting CPU would also need the masks refreshed when such a CPU comes online. On T8140 the G17 GPU completion thread used to run on CPU 5 most of the time and now stays on CPU 0. Over two boots of each kernel, compute submission at depth 2 goes from 47-49 us to 37 us per submit, and render submission at depth 1 from a CPU 0 submitter from 128 us to 108 us. Other submission rows are unchanged. (cherry picked from commit 28ceabd) Signed-off-by: DjDeveloperr <hey@djdev.me>
4e2f3fa to
1a10581
Compare
|
Thanks for the careful review. Changes are in 1a10581:
It builds cleanly on 7.2.11 with the Neo config. I'll post the Neo boot of the 7.2.11 build showing |
maralcbr
left a comment
There was a problem hiding this comment.
Reviewed at 4e2f3fa (source only; no build or boot on my side).
The approach matches what the hardware does: on AICv2+ the hard IRQ lands wherever SIQ_CFG allows, so reporting that set as the effective affinity is the right way to get threaded handlers onto the same CPU. Gating the SIQ_CFG read to T8140 at EL2, and making the reported set independent of which CPUs are online, both look correct. CPU0 offline isn't reachable on Neo with spin-table.
Before merge:
- Test the exact build. The PR body says the Neo boots used a build without the per-SoC gate, and the gated build was compile-checked only. Please boot this head on Neo (on 7.2.11, which the body also says is still to come) and re-check
irq/65placement andeffective_affinity_list. - Every AICv2+ SoC changes, not just Neo.
aic2_chipnow hasirq_set_affinity, so on M1 Pro/Max, M2, M3 and later,request_irq(), irqbalance and/proc/irq/*/smp_affinitywrites now succeed (without steering anything), and the effective mask becomescpu_possible_mask. That should behave like today, but it is untested. I'll boot it on our M2 lab machine; a run on an M1 Pro/Max or M3 would also help. - Design call for the maintainers. On Neo every threaded AIC handler (GPU, NVMe, Wi-Fi, …) is now pinned to CPU0, a Tahiti E-core (capacity 712). The numbers show a clear win for GPU latency and CPU0 at ~70% under render d32, but combined I/O + GPU load hasn't been measured. Worth a run with NVMe and Wi-Fi traffic alongside the GPU benchmark.
As with #226 and #227, one combined run on the exact base with all three applied would be the most useful evidence, since they all act on the same utilisation and frequency loop.
chriskearney
left a comment
There was a problem hiding this comment.
Source review at 1a10581c385e8a78f966824be91d8f03072ce2ba: the separate routed irqchip addresses the shared-path concern in the earlier review. M3 Pro's T6030 compatible still selects aic3_info with siq_cfg_routing=false, retaining the original aic2_chip without an affinity callback. Both the SIQ register read and new routed-chip selection require the T8140 flag plus EL2.
I found no new source blocker in that gate or the unchanged M3 IRQ/MSI path. The comment correctly limits the current mask scheme to the single-accepting-CPU case; adding another accepting CPU/SoC later would require refreshing existing effective masks.
Remaining target qualification is the final 7.2.11 Neo build: actual effective affinity/thread placement, ordinary and PCIe MSI device operation, and suspend/resume. A clean M3 regression run cannot validate the Neo register access or latency gain.
Validation here is source review of the changed controller paths and M3 Pro DT compatibility. No independent physical boot of this head yet.
|
Thanks. This review was at 4e2f3fa; the current head is 1a10581.
|
maralcbr
left a comment
There was a problem hiding this comment.
Follow-up at 1a10581 (source only).
Two corrections to my previous review. I posted it against 4e2f3fa after you had already pushed this head, so the "every AICv2+ SoC changes" point was already addressed. And you're right about CPU 0: Linux makes the boot CPU logical CPU 0, and Neo boots on the P-core at cpu@10100. I had read the DT order as the logical order. Sorry about both.
The separate aic2_routed_chip, selected only when siq_cfg_routing is set at EL2, keeps every other SoC on the unchanged aic2_chip, and the single-accepting-CPU limitation is documented where a future SoC would need it. No further code comments; I agree with Chris's source review.
Ready to merge from my side once the combined 7.2.11 Neo run reports thread placement and effective_affinity_list, ordinary and PCIe MSI device operation under combined NVMe + Wi-Fi + GPU load, and suspend/resume.
|
Results from the combined 7.2.11 Neo run with this head (1a10581), together with #226 and #227:
All of these interrupts share CPU0, because SIQ_CFG accepts only that CPU here. Under the combined I/O load, render.light d2 on omacom's asahi driver was 22% slower (66.1 → 80.8 µs per submit). |
maralcbr
left a comment
There was a problem hiding this comment.
Thanks for the combined run at 1a10581. Placement and device health look good: effective affinity 0 for the mailbox, NVMe and Wi-Fi MSI, no timeouts or resets under load, and it holds across both suspend cycles.
Two things before merge:
-
Which handler does this actually move? The run says irq 65 "is not threaded", but the PR's premise is that the threaded GPU completion handler (
irq/65) floated to the app's CPU, and the gains were explained that way. This change only affects threaded handlers: the hard IRQ already arrives only on CPU 0 because of SIQ_CFG, and this PR just reports that. Could you list the actual handler and thread identities involved (e.g.ps -eLo pid,psr,comm | grep irq/,/proc/irq/*/actions, and which kthread or workqueue runs GPU completion), with where each ran before and after? If irq 65 has no thread, what explains the latency gains? -
A matched comparison under combined load. "render.light d2 22% slower (66.1 → 80.8 µs) under combined I/O" compares load against no load; it doesn't show whether this change makes contention better or worse. Please run the same combined NVMe + Wi-Fi + GPU load with and without #228 on the same base, keeping #226 and #227 fixed, and report the GPU latency rows, I/O throughput and CPU 0 utilisation. Average utilisation (29.8%) can't rule out burst contention, so per-interval peaks or
irq/thread run-delay would help.
Real suspend/resume also stays unqualified until s2idle resumes on Neo. That's fine to defer, as on #227.
|
Good catch. The combined run couldn't show the effect in the description, and I should have spelled out why. 1. Which handler.
I'll update the description so the GPU latency gains are scoped to trees where the GPU mailbox handler is threaded. I'll also post the thread identities and placement with and without this PR on both trees: 2. Matched comparison. Agreed. I'll run the same combined NVMe + Wi-Fi + GPU load with and without this PR, on the same base with #226 and #227 fixed. I'll report the GPU rows, I/O throughput, CPU 0 utilisation per interval with peaks, and run delay for the |
|
Both asks, measured on Neo. Each variant was built in the same tree, with one one-shot boot each:
vmlinux BTF differs between the two, so each variant got its own modules. The aurora-silicon 7.3 kernel was captured for identities only. 1. Thread identities and placement
2. Matched combined load: an NVMe direct write/read loop plus a Wi-Fi download loop, with GPU rows run under it.
The other irq threads did not run in the window. Reading the table:
|
maralcbr
left a comment
There was a problem hiding this comment.
Approving at 1a10581.
Thanks for the thread identities and the matched runs. They answer both questions. On integration/aurora the GPU mailbox is a hard IRQ, so GPU completion doesn't move. The change reports effective affinity correctly (34 of 57 AIC2 IRQs) and co-locates the existing threaded handlers with their hard IRQ on CPU 0. Under combined load, CPU 0 peaked at 78%, with no samples at or above 90%, and the irq/95 run-delay increase (0.02 → 0.11 µs per run) is negligible. The description now scopes the GPU latency gains to the aurora-silicon tree with the threaded mailbox, which matches the evidence.
One note, not blocking: NVMe direct writes came out about 9% lower with the change (median 1000 vs 1100 MB/s). With one boot per side that can't be called noise, and it can't be pinned on this PR either. If you get the chance, three alternating A/B boots on just the write leg would settle it.
Summary
AICv2+ has no per-IRQ CPU target. Each CPU says in its SIQ_CFG system register whether it accepts IRQs; routing field (bits 1:0) = 3 means it never receives one. On T8140 (Neo) CPU 0 reads 0 and every secondary reads 3, so every hardware IRQ lands on CPU 0. Because the AICv2 irqchip has no
irq_set_affinity, the effective affinity stayed empty, and threaded handlers floated to any CPU. Where the G17 GPU's mailbox receive IRQ is threaded, the GPU completion thread (irq/65) often ran on the app's CPU, which saturated it and caused a bimodal "slow mode" in latency-sensitive GPU workloads. That is the case on the aurora-silicon tree, where the measurements below were taken.On integration/aurora, the GPU mailbox receive IRQ is a plain hard IRQ (also with #229), so GPU completions already run on CPU 0 and this change does not move them. There it binds the threaded handlers that do exist, such as Wi-Fi MSI (mt7932), dockchannel, Bluetooth, AVD, audio and ISP, to CPU 0, where their hard IRQs arrive, and reports the effective affinity correctly. The GPU latency gains below apply once the GPU mailbox handler is threaded.
On SoCs whose routing has been verified on hardware (currently T8140), and only at EL2, AICv2+ IRQs map to a second irqchip,
aic2_routed_chip. It records at CPU bring-up which CPUs accept IRQs and reports them as each IRQ's effective affinity, so genirq binds threaded handlers to the CPU(s) the hard IRQ actually arrives on.Safety and scope:
apple,t8140-aic3).aic2_chipbyte for byte.smp_affinitywrites still fail there, and MSI affinity calls don't reach AIC.Behaviour change on T8140 (at EL2): every ordinary AIC IRQ thread, PCIe MSI ones included, now runs on CPU 0, and
smp_affinitywrites don't move it. They are accepted and kept only for reporting. The CPU 0 load numbers below cover this for gpubench.Hardware evidence (Neo, T8140, aurora-silicon 7.3 tree with the threaded GPU mailbox)
Routing probe on a spare AIC line:
New core:
irq/65on CPU 0 in 100% of 50 ms samples, and/proc/irq/65/effective_affinity_list= 0. On the old core it was on CPU 5 64–99% of the time, with an empty effective affinity.Benchmarks (gpubench, µs/submit, 2 new-core boots vs 3 old-core boots; slow rounds in brackets):
Slow rows overall: 20/30 + 20/30 + 54/80 → 8/30 + 12/80. Other rows are unchanged.
CPU 0 load: not overloaded (69–70% busy under render d32 vs 54–57% before), and its 1 ms timer lateness improved (p99 81–83 µs vs 105–115).
Base
The same change on 7.2.11; it applies unchanged. The measurements above were taken on Neo with the 7.3 kernel. This branch builds cleanly on 7.2.11 with the Neo config; a Neo boot test of the 7.2.11 build follows.