Message ID | bc8183fcd7f6ca07b0d56aa8061d816485b84341.1669822447.git.robin.murphy@arm.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:f944:0:0:0:0:0 with SMTP id q4csp1000820wrr; Wed, 30 Nov 2022 07:44:09 -0800 (PST) X-Google-Smtp-Source: AA0mqf70uIHjrMxsW/GH9wn9d5kjzRMCHmaOyyeJd6HQfwJVkmWzihxn6nn9UIniCPDKoMsm9sVd X-Received: by 2002:aa7:c719:0:b0:46a:bfd0:f816 with SMTP id i25-20020aa7c719000000b0046abfd0f816mr23004836edq.277.1669823049833; Wed, 30 Nov 2022 07:44:09 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1669823049; cv=none; d=google.com; s=arc-20160816; b=TQZzw9BQg/2BacQf4fyCnfvA5+EbQsTxURG8Vb0b79genigPvSRzx33SDmSdZfVOFV /MrYLod5q6O7a5vEZNK44LGYxmEoma4ov5fo8ryHzVbR37f53oZwDxbWlL73QSxyeXZu woaXuMmqkpizE89NvRxCgRCnKAKOHNvrCwIDYnby2/6FTszTWlbV3qN5dRVV73l4zytH HMyUh3V0Fy8Ib+wcqzTHIJE8jYdGaxGf4HCHXUX9QXTYB2jS0HlshbD7uU3tEIIZpdvx X7sspYPMS3Wl69ar7pRl9UP40ql+a+f08AXptE3okJ6eLEC0oeDt5D33+n+OZNfiLeLA NiTg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from; bh=ZI8eyYc7bcYKHsbj+xdLTVIi9edDK3QqeLwe6+vjYKU=; b=yp4qG+99II/IzbkGTTcXVGZ/amGShXToBv+dW9clIY7vkPkEAVaNHz1F7bdAAno6MJ l6WV6xw5CClwsoIPrIZYesToJ3uPp0zsMf6ZdwULFel0yEHV0g3xGrs4ZyoYOHXoPyrc moJ8PMcoF85eZefVqhKLj2mfcw5RqS5r9lAOPNWOvBMSphEoQ/phavohlyc4zCicTqoI qO/T0XGTuei1ppjFJv7tBIE4sfdhiKg7xnwjgT+F8rgVmQKmjyKTyAgSlT4a9bMF0B29 VSnY8wZPMlnTNqFPesfBaqwy9gqzHBX3Kac5Px+zeaZ69+rJFeV6BSlw967QKe/J0Kt+ 3ucQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=arm.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id z21-20020a05640235d500b0045d4a88c735si1891513edc.220.2022.11.30.07.43.44; Wed, 30 Nov 2022 07:44:09 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=arm.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229921AbiK3PhQ (ORCPT <rfc822;heyuhang3455@gmail.com> + 99 others); Wed, 30 Nov 2022 10:37:16 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59426 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229601AbiK3Pgr (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Wed, 30 Nov 2022 10:36:47 -0500 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id C7FA51C43D for <linux-kernel@vger.kernel.org>; Wed, 30 Nov 2022 07:36:45 -0800 (PST) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 30EE4D6E; Wed, 30 Nov 2022 07:36:52 -0800 (PST) Received: from e121345-lin.cambridge.arm.com (e121345-lin.cambridge.arm.com [10.1.196.40]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id C77E33F67D; Wed, 30 Nov 2022 07:36:44 -0800 (PST) From: Robin Murphy <robin.murphy@arm.com> To: will@kernel.org, mark.rutland@arm.com Cc: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Geoff Blake <blakgeof@amazon.com> Subject: [PATCH 1/2] perf/arm-cmn: Cope with spurious IRQs better Date: Wed, 30 Nov 2022 15:36:38 +0000 Message-Id: <bc8183fcd7f6ca07b0d56aa8061d816485b84341.1669822447.git.robin.murphy@arm.com> X-Mailer: git-send-email 2.36.1.dirty MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_NONE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1750936374314888355?= X-GMAIL-MSGID: =?utf-8?q?1750936374314888355?= |
Series |
[1/2] perf/arm-cmn: Cope with spurious IRQs better
|
|
Commit Message
Robin Murphy
Nov. 30, 2022, 3:36 p.m. UTC
Although we reset the CMN state during probe before requesting our
interrupt(s), a pending IRQ could already have been latched at the
interrupt controller, and thus be delivered spuriously as soon as the
IRQ is enabled. Not handling that can then lead to the IRQ layer
disabling it again, and things subseuqently going wonky.
Since we can't support shared IRQs anyway for affinity-management
reasons, the only time we should concievably return IRQ_NONE is for a
spurious interrupt which did somehow originate from our CMN, so there
should be no harm in removing the problem by simply claiming to have
handled those as well.
Reported-by: Geoff Blake <blakgeof@amazon.com>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
---
drivers/perf/arm-cmn.c | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
Comments
Robin, From my perspective, this is a worse solution as now we're sweeping an issue under the rug and consuming CPU cycles handling IRQs we should not be getting in the first place. While an overflow IRQ from the cmn should not be high frequency, there is a non-zero chance in the future it could be and this could lead to a very hard to debug performance issue instead of the current problem, which is discovering we need to clean up better from a noisy kernel message. The driver as best I can grok currently is optimized to limit the amount of register writes for the common use-case, which is setting and unsetting events, so all the wiring for the PMU to feed events to the DTC is done up front on load: DTC_CTL's DT_EN bit is set immediately during probe, as is OVFL_INTR_EN. All the DN states and DTM PMU_CONFIG_PMU_EN is deferred for when an event is actually set, and here we go through all of them anyways for each event unless its bynodeid, so the expense of setting events grows linearly with the mesh size anyways. Doing a one time scan on remove/shutdown to reset everything to a clean state seems like a wash performance wise and wanting to keep just a single register write doesn't make much sense to me. Should I instead pull this step out as a generic initialization step that can live as a single chunk of code to make it better for maintainability? Would that be a better solution? - Geoff
On 2022-11-30 16:02, Geoff Blake wrote: > Robin, > > From my perspective, this is a worse solution as now we're sweeping an > issue under the rug and consuming CPU cycles handling IRQs we should not > be getting in the first place. While an overflow IRQ from the cmn should > not be high frequency, there is a non-zero chance in the future it could > be and this could lead to a very hard to debug performance issue instead > of the current problem, which is discovering we need to clean up better > from a noisy kernel message. Kexec is not the only possible source of spurious IRQs. If they cause a problem for this driver, that cannot be robustly addressed by trying to rely on whatever software might happen to run before this driver. > The driver as best I can grok currently is optimized to limit the amount > of register writes for the common use-case, which is setting and unsetting > events, so all the wiring for the PMU to feed events to the DTC is done up > front on load: DTC_CTL's DT_EN bit is set immediately during probe, as is > OVFL_INTR_EN. All the DN states and DTM PMU_CONFIG_PMU_EN is deferred > for when an event is actually set, and here we go through all of them > anyways for each event unless its bynodeid, so the expense of setting > events grows linearly with the mesh size anyways. If arm_cmn_init_dtc() writing 0 to PMCR didn't stop the PMU then we've got bigger problems, because that's how we expect to start and stop it in normal operation. I'm not ruling out that some subtle bug in that regard might exist, since I've still not yet had a chance to reproduce and observe this behaviour on my board, but I've also not seen sufficient evidence to suggest that that is the case either. (Now that I'm looking closely, I think there *is* actually a small oversight for the DTMs, but that would lead to different symptoms than you reported) At least the writes to PMOVSR_CLR *did* clearly work, because you're seeing the "nobody cared" message from the IRQ core rather than the WARN_ON(!dtc->counters[i]) which would happen if a fresh overflow was actually asserted. Currently I would expect to see up to 4 of those messages since there can be up to 4 IRQs, but once those are all requested, enabled, and "handled", all the spurious initially-latched state should be cleared and any *new* overflows will be indicated in PMOVSR. I don't see how a single IRQ could ever be unhandled more than once anyway, if the first time disables it. Thanks, Robin.
> > From my perspective, this is a worse solution as now we're sweeping an > > issue under the rug and consuming CPU cycles handling IRQs we should not > > be getting in the first place. While an overflow IRQ from the cmn should > > not be high frequency, there is a non-zero chance in the future it could > > be and this could lead to a very hard to debug performance issue instead > > of the current problem, which is discovering we need to clean up better > > from a noisy kernel message. > > Kexec is not the only possible source of spurious IRQs. If they cause a > problem for this driver, that cannot be robustly addressed by trying to > rely on whatever software might happen to run before this driver. Sure, I can agree with the assertion a spurious IRQ could come from anywhere, in that case though, shouldn't the behavior still be to log spurious IRQs as a warning instead of silently sinking them? > > The driver as best I can grok currently is optimized to limit the amount > > of register writes for the common use-case, which is setting and unsetting > > events, so all the wiring for the PMU to feed events to the DTC is done up > > front on load: DTC_CTL's DT_EN bit is set immediately during probe, as is > > OVFL_INTR_EN. All the DN states and DTM PMU_CONFIG_PMU_EN is deferred > > for when an event is actually set, and here we go through all of them > > anyways for each event unless its bynodeid, so the expense of setting > > events grows linearly with the mesh size anyways. > > If arm_cmn_init_dtc() writing 0 to PMCR didn't stop the PMU then we've > got bigger problems, because that's how we expect to start and stop it > in normal operation. I'm not ruling out that some subtle bug in that > regard might exist, since I've still not yet had a chance to reproduce > and observe this behaviour on my board, but I've also not seen > sufficient evidence to suggest that that is the case either. (Now that > I'm looking closely, I think there *is* actually a small oversight for > the DTMs, but that would lead to different symptoms than you reported) > At least the writes to PMOVSR_CLR *did* clearly work, because you're > seeing the "nobody cared" message from the IRQ core rather than the > WARN_ON(!dtc->counters[i]) which would happen if a fresh overflow was > actually asserted. Currently I would expect to see up to 4 of those > messages since there can be up to 4 IRQs, but once those are all > requested, enabled, and "handled", all the spurious initially-latched > state should be cleared and any *new* overflows will be indicated in > PMOVSR. I don't see how a single IRQ could ever be unhandled more than > once anyway, if the first time disables it. I do see 4 of these "nobody cared" messages in all the times I've reproduced it, but saw no need to copy paste all of them in with the original post. Looking back over the code I see why more clearly your assertion we only need to clear the DT_EN bit as the PMU is off at the DTC with the PMCR set to 0 on init, but it is really hard to see why that is with all the various places bits of configuration is done, but it is still not easy to verify if unsetting that bit is sufficient to not get into some odd corner cases. Is there any argument against me taking another pass and try separating out discovery, from a shared reset/initialization code path? -Geoff
On 2022-11-30 23:13, Geoff Blake wrote: > >>> From my perspective, this is a worse solution as now we're sweeping an >>> issue under the rug and consuming CPU cycles handling IRQs we should not >>> be getting in the first place. While an overflow IRQ from the cmn should >>> not be high frequency, there is a non-zero chance in the future it could >>> be and this could lead to a very hard to debug performance issue instead >>> of the current problem, which is discovering we need to clean up better >>> from a noisy kernel message. >> >> Kexec is not the only possible source of spurious IRQs. If they cause a >> problem for this driver, that cannot be robustly addressed by trying to >> rely on whatever software might happen to run before this driver. > > Sure, I can agree with the assertion a spurious IRQ could come from > anywhere, in that case though, shouldn't the behavior still be to log > spurious IRQs as a warning instead of silently sinking them? We still have to handle the interrupt anyway to avoid it getting disabled behind our back, and beyond that it's not really something that's actionable by the user. What would we say? dev_warn(dev, "Something harmless, and in some cases expected, happened! If you've just rebooted after a kernel panic, maybe try having the kernel not panic?"); Perhaps that should be a core IRQ helper so that many other drivers can also call it too? Furthermore if you're worried about performance implications from a theoretical interrupt storm, I can tell you from experience that logging to a serial console from a high-frequency interrupt handler is one of the best ways to cripple a system to the point where reaching for the power switch is the only option. >>> The driver as best I can grok currently is optimized to limit the amount >>> of register writes for the common use-case, which is setting and unsetting >>> events, so all the wiring for the PMU to feed events to the DTC is done up >>> front on load: DTC_CTL's DT_EN bit is set immediately during probe, as is >>> OVFL_INTR_EN. All the DN states and DTM PMU_CONFIG_PMU_EN is deferred >>> for when an event is actually set, and here we go through all of them >>> anyways for each event unless its bynodeid, so the expense of setting >>> events grows linearly with the mesh size anyways. >> >> If arm_cmn_init_dtc() writing 0 to PMCR didn't stop the PMU then we've >> got bigger problems, because that's how we expect to start and stop it >> in normal operation. I'm not ruling out that some subtle bug in that >> regard might exist, since I've still not yet had a chance to reproduce >> and observe this behaviour on my board, but I've also not seen >> sufficient evidence to suggest that that is the case either. (Now that >> I'm looking closely, I think there *is* actually a small oversight for >> the DTMs, but that would lead to different symptoms than you reported) > >> At least the writes to PMOVSR_CLR *did* clearly work, because you're >> seeing the "nobody cared" message from the IRQ core rather than the >> WARN_ON(!dtc->counters[i]) which would happen if a fresh overflow was >> actually asserted. Currently I would expect to see up to 4 of those >> messages since there can be up to 4 IRQs, but once those are all >> requested, enabled, and "handled", all the spurious initially-latched >> state should be cleared and any *new* overflows will be indicated in >> PMOVSR. I don't see how a single IRQ could ever be unhandled more than >> once anyway, if the first time disables it. > > I do see 4 of these "nobody cared" messages in all the times I've > reproduced it, but saw no need to copy paste all of them in with the > original post. For reference, the key point to look for is that the IRQ numbers are different, so it's not actually the *same* message multiple times, it's multiple messages representing distinct causes. > Looking back over the code I see why more clearly your > assertion we only need to clear the DT_EN bit as the PMU is off at > the DTC with the PMCR set to 0 on init, but it is really hard to > see why that is with all the various places bits of configuration is done, > but it is still not easy to verify if unsetting that bit is sufficient to > not get into some odd corner cases. The DTC_CTL documentation seems fairly unambiguous: [0] dt_en Enables debug, trace, and PMU features The design intent is that the PMU counters do not count when the entire PMU feature is disabled. I'm pretty sure I did confirm that empirically during development too (I recall the sheer number of different "enable" bits baffled me at the beginning, and there was actually one that did nothing, which I think did eventually get removed from the documentation). Of course clearing PMCR_PMU_EN is sufficient to simply stop counting, which we also depend on for correct operation, but I believe clearing DT_EN allows it to put all of the DT logic into a quiescent state. > Is there any argument against me taking another pass and try separating > out discovery, from a shared reset/initialization code path? Frankly, yes. Discovery and initialisation are already as distinct as I could realistically make them: err = arm_cmn_discover(cmn, rootnode); if (err) return err; err = arm_cmn_init_dtcs(cmn); if (err) return err; err = arm_cmn_init_irqs(cmn); if (err) return err; It's true that that DTM initialisation is still entangled in the middle of the discovery walk, but the alternatives are to separately initialise the DTM data and registers from two different places (yuck), or duplicate a chunk of the discovery process in a subsequent initialisation phase to establish the XP/DTM relationship (also yuck). With the possible exception of arm_cmn_init_irqs() which may contain a teeny bit of self-indulgence, I've tried hard to make most of the code as concise and clear as it can be within the bounds of the perf API and the way the CMN PMU fundamentally works, also balanced with trying to keep memory footprint and runtime overhead from getting out of hand (still a bit of a losing battle when updating a single event count might require 148 register reads...). I accept that it's a bit of a personal preference that less code is easier to follow than more code, but I don't think it's a particularly rare one, and the fact is that the way the CMN PMU works is rather complicated compared to most other PMUs, so that brings an unavoidable level of complexity to the driver, and adding more code to do additional unnecessary work cannot conceivably make it any less complex. Thanks, Robin.
> > > > From my perspective, this is a worse solution as now we're sweeping an > > > > issue under the rug and consuming CPU cycles handling IRQs we should not > > > > be getting in the first place. While an overflow IRQ from the cmn > > > > should > > > > not be high frequency, there is a non-zero chance in the future it could > > > > be and this could lead to a very hard to debug performance issue instead > > > > of the current problem, which is discovering we need to clean up better > > > > from a noisy kernel message. > > > > > > Kexec is not the only possible source of spurious IRQs. If they cause a > > > problem for this driver, that cannot be robustly addressed by trying to > > > rely on whatever software might happen to run before this driver. > > > > Sure, I can agree with the assertion a spurious IRQ could come from > > anywhere, in that case though, shouldn't the behavior still be to log > > spurious IRQs as a warning instead of silently sinking them? > > We still have to handle the interrupt anyway to avoid it getting > disabled behind our back, and beyond that it's not really something > that's actionable by the user. What would we say? > > dev_warn(dev, "Something harmless, and in some cases expected, > happened! If you've just rebooted after a kernel panic, maybe try having > the kernel not panic?"); > > Perhaps that should be a core IRQ helper so that many other drivers can > also call it too? > > Furthermore if you're worried about performance implications from a > theoretical interrupt storm, I can tell you from experience that logging > to a serial console from a high-frequency interrupt handler is one of > the best ways to cripple a system to the point where reaching for the > power switch is the only option. Logging unexpected events is necessary to give clues of what is going wrong before they implode on fully remote machines. If you prefer to handle the IRQ here rather than in the bad_irq section, then can we at least have a WARN_ON() in the case where a spurious IRQ happens but no overflow bit is set. > The DTC_CTL documentation seems fairly unambiguous: > > [0] dt_en Enables debug, trace, and PMU features > > The design intent is that the PMU counters do not count when the entire > PMU feature is disabled. I'm pretty sure I did confirm that empirically > during development too (I recall the sheer number of different "enable" > bits baffled me at the beginning, and there was actually one that did > nothing, which I think did eventually get removed from the documentation). > > Of course clearing PMCR_PMU_EN is sufficient to simply stop counting, > which we also depend on for correct operation, but I believe clearing > DT_EN allows it to put all of the DT logic into a quiescent state. I took the other patch that writes 0 to DTC_CTL.dt_en only and put it in a loop of kexec'ing when the PMU is active for a few hours, I did not see anymore spurious IRQs (whereas with the stock driver I could reproduce in under 10 tries). You are correct Robin, that is all that is needed, and my code was overly cautious. - Geoff
diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index b80a9b74662b..9e8be5586423 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -1798,7 +1798,6 @@ static int arm_cmn_pmu_offline_cpu(unsigned int cpu, struct hlist_node *cpuhp_no static irqreturn_t arm_cmn_handle_irq(int irq, void *dev_id) { struct arm_cmn_dtc *dtc = dev_id; - irqreturn_t ret = IRQ_NONE; for (;;) { u32 status = readl_relaxed(dtc->base + CMN_DT_PMOVSR); @@ -1807,7 +1806,6 @@ static irqreturn_t arm_cmn_handle_irq(int irq, void *dev_id) for (i = 0; i < CMN_DTM_NUM_COUNTERS; i++) { if (status & (1U << i)) { - ret = IRQ_HANDLED; if (WARN_ON(!dtc->counters[i])) continue; delta = (u64)arm_cmn_read_counter(dtc, i) << 16; @@ -1816,7 +1814,6 @@ static irqreturn_t arm_cmn_handle_irq(int irq, void *dev_id) } if (status & (1U << CMN_DT_NUM_COUNTERS)) { - ret = IRQ_HANDLED; if (dtc->cc_active && !WARN_ON(!dtc->cycles)) { delta = arm_cmn_read_cc(dtc); local64_add(delta, &dtc->cycles->count); @@ -1826,7 +1823,7 @@ static irqreturn_t arm_cmn_handle_irq(int irq, void *dev_id) writel_relaxed(status, dtc->base + CMN_DT_PMOVSR_CLR); if (!dtc->irq_friend) - return ret; + return IRQ_HANDLED; dtc += dtc->irq_friend; } }