From patchwork Wed Jun 13 00:57:36 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ricardo Neri X-Patchwork-Id: 928547 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=sparclinux-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=linux.intel.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 4157n12k6fz9s0w for ; Wed, 13 Jun 2018 11:04:05 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935085AbeFMBDv (ORCPT ); Tue, 12 Jun 2018 21:03:51 -0400 Received: from mga14.intel.com ([192.55.52.115]:64688 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934989AbeFMBBk (ORCPT ); Tue, 12 Jun 2018 21:01:40 -0400 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga008.fm.intel.com ([10.253.24.58]) by fmsmga103.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Jun 2018 18:01:34 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.51,216,1526367600"; d="scan'208";a="47283511" Received: from voyager.sc.intel.com ([10.3.52.149]) by fmsmga008.fm.intel.com with ESMTP; 12 Jun 2018 18:01:34 -0700 From: Ricardo Neri To: Thomas Gleixner , Ingo Molnar , "H. Peter Anvin" Cc: Andi Kleen , Ashok Raj , Borislav Petkov , Tony Luck , "Ravi V. Shankar" , x86@kernel.org, sparclinux@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, linux-kernel@vger.kernel.org, Ricardo Neri , Jacob Pan , "Rafael J. Wysocki" , Don Zickus , Nicholas Piggin , Michael Ellerman , Frederic Weisbecker , Alexei Starovoitov , Babu Moger , Mathieu Desnoyers , Masami Hiramatsu , Peter Zijlstra , Andrew Morton , Philippe Ombredanne , Colin Ian King , Byungchul Park , "Paul E. McKenney" , "Luis R. Rodriguez" , Waiman Long , Josh Poimboeuf , Randy Dunlap , Davidlohr Bueso , Christoffer Dall , Marc Zyngier , Kai-Heng Feng , Konrad Rzeszutek Wilk , David Rientjes , iommu@lists.linux-foundation.org Subject: [RFC PATCH 16/23] watchdog/hardlockup: Add an HPET-based hardlockup detector Date: Tue, 12 Jun 2018 17:57:36 -0700 Message-Id: <1528851463-21140-17-git-send-email-ricardo.neri-calderon@linux.intel.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1528851463-21140-1-git-send-email-ricardo.neri-calderon@linux.intel.com> References: <1528851463-21140-1-git-send-email-ricardo.neri-calderon@linux.intel.com> Sender: sparclinux-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: sparclinux@vger.kernel.org This is the initial implementation of a hardlockup detector driven by an HPET timer. This initial implementation includes functions to control the timer via its registers. It also requests such timer, installs a minimal interrupt handler and performs the initial configuration of the timer. The detector is not functional at this stage. Subsequent changesets will populate the NMI watchdog operations and register it with the lockup detector. This detector depends on HPET_TIMER since platform code performs the initialization of the timer and maps its registers to memory. It depends on HPET to compute the ticks per second of the timer. Cc: Ashok Raj Cc: Andi Kleen Cc: Tony Luck Cc: Borislav Petkov Cc: Jacob Pan Cc: "Rafael J. Wysocki" Cc: Don Zickus Cc: Nicholas Piggin Cc: Michael Ellerman Cc: Frederic Weisbecker Cc: Alexei Starovoitov Cc: Babu Moger Cc: Mathieu Desnoyers Cc: Masami Hiramatsu Cc: Peter Zijlstra Cc: Andrew Morton Cc: Philippe Ombredanne Cc: Colin Ian King Cc: Byungchul Park Cc: "Paul E. McKenney" Cc: "Luis R. Rodriguez" Cc: Waiman Long Cc: Josh Poimboeuf Cc: Randy Dunlap Cc: Davidlohr Bueso Cc: Christoffer Dall Cc: Marc Zyngier Cc: Kai-Heng Feng Cc: Konrad Rzeszutek Wilk Cc: David Rientjes Cc: "Ravi V. Shankar" Cc: x86@kernel.org Cc: iommu@lists.linux-foundation.org Signed-off-by: Ricardo Neri --- kernel/Makefile | 1 + kernel/watchdog_hld_hpet.c | 334 +++++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 10 ++ 3 files changed, 345 insertions(+) create mode 100644 kernel/watchdog_hld_hpet.c diff --git a/kernel/Makefile b/kernel/Makefile index 0a0d86d..73c79b2 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_KGDB) += debug/ obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o obj-$(CONFIG_HARDLOCKUP_DETECTOR_PERF) += watchdog_hld.o watchdog_hld_perf.o +obj-$(CONFIG_HARDLOCKUP_DETECTOR_HPET) += watchdog_hld.o watchdog_hld_hpet.o obj-$(CONFIG_SECCOMP) += seccomp.o obj-$(CONFIG_RELAY) += relay.o obj-$(CONFIG_SYSCTL) += utsname_sysctl.o diff --git a/kernel/watchdog_hld_hpet.c b/kernel/watchdog_hld_hpet.c new file mode 100644 index 0000000..8fa4e55 --- /dev/null +++ b/kernel/watchdog_hld_hpet.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A hardlockup detector driven by an HPET timer. + * + * Copyright (C) Intel Corporation 2018 + */ + +#define pr_fmt(fmt) "NMI hpet watchdog: " fmt + +#include +#include +#include + +#undef pr_fmt +#define pr_fmt(fmt) "NMI hpet watchdog: " fmt + +static struct hpet_hld_data *hld_data; + +/** + * get_count() - Get the current count of the HPET timer + * + * Returns: + * + * Value of the main counter of the HPET timer + */ +static inline unsigned long get_count(void) +{ + return hpet_readq(HPET_COUNTER); +} + +/** + * set_comparator() - Update the comparator in an HPET timer instance + * @hdata: A data structure with the timer instance to update + * @cmp: The value to write in the in the comparator registere + * + * Returns: + * + * None + */ +static inline void set_comparator(struct hpet_hld_data *hdata, + unsigned long cmp) +{ + hpet_writeq(cmp, HPET_Tn_CMP(hdata->num)); +} + +/** + * kick_timer() - Reprogram timer to expire in the future + * @hdata: A data structure with the timer instance to update + * + * Reprogram the timer to expire within watchdog_thresh seconds in the future. + * + * Returns: + * + * None + */ +static void kick_timer(struct hpet_hld_data *hdata) +{ + unsigned long new_compare, count; + + /* + * Update the comparator in increments of watch_thresh seconds relative + * to the current count. Since watch_thresh is given in seconds, we + * are able to update the comparator before the counter reaches such new + * value. + * + * Let it wrap around if needed. + */ + count = get_count(); + + new_compare = count + watchdog_thresh * hdata->ticks_per_second; + + set_comparator(hdata, new_compare); +} + +/** + * disable() - Disable an HPET timer instance + * @hdata: A data structure with the timer instance to disable + * + * Returns: + * + * None + */ +static void disable(struct hpet_hld_data *hdata) +{ + unsigned int v; + + v = hpet_readl(HPET_Tn_CFG(hdata->num)); + v &= ~HPET_TN_ENABLE; + hpet_writel(v, HPET_Tn_CFG(hdata->num)); +} + +/** + * enable() - Enable an HPET timer instance + * @hdata: A data structure with the timer instance to enable + * + * Returns: + * + * None + */ +static void enable(struct hpet_hld_data *hdata) +{ + unsigned long v; + + /* Clear any previously active interrupt. */ + hpet_writel(BIT(hdata->num), HPET_STATUS); + + v = hpet_readl(HPET_Tn_CFG(hdata->num)); + v |= HPET_TN_ENABLE; + hpet_writel(v, HPET_Tn_CFG(hdata->num)); +} + +/** + * set_periodic() - Set an HPET timer instance in periodic mode + * @hdata: A data structure with the timer instance to enable + * + * If the timer supports periodic mode, configure it in such mode. + * Returns: + * + * None + */ +static void set_periodic(struct hpet_hld_data *hdata) +{ + unsigned long v; + + v = hpet_readl(HPET_Tn_CFG(hdata->num)); + if (v & HPET_TN_PERIODIC_CAP) { + v |= HPET_TN_PERIODIC; + hpet_writel(v, HPET_Tn_CFG(hdata->num)); + hdata->flags |= HPET_DEV_PERI_CAP; + } +} + +/** + * is_hpet_wdt_interrupt() - Determine if an HPET timer caused interrupt + * @hdata: A data structure with the timer instance to enable + * + * To be used when the timer is programmed in level-triggered mode, determine + * if an instance of an HPET timer indicates that it asserted an interrupt by + * checking the status register. + * + * Returns: + * + * True if a level-triggered timer asserted an interrupt. False otherwise. + */ +static bool is_hpet_wdt_interrupt(struct hpet_hld_data *hdata) +{ + unsigned long this_isr; + unsigned int lvl_trig; + + this_isr = hpet_readl(HPET_STATUS) & BIT(hdata->num); + + lvl_trig = hpet_readl(HPET_Tn_CFG(hdata->num)) & HPET_TN_LEVEL; + + if (lvl_trig && this_isr) + return true; + + return false; +} + +/** + * hardlockup_detector_irq_handler() - Interrupt handler + * @irq: Interrupt number + * @data: Data associated with the interrupt + * + * A simple interrupt handler. Simply kick the timer and acknowledge the + * interrupt. + * + * Returns: + * + * IRQ_NONE if the HPET timer did not cause the interrupt. IRQ_HANDLED + * otherwise. + */ +static irqreturn_t hardlockup_detector_irq_handler(int irq, void *data) +{ + struct hpet_hld_data *hdata = data; + unsigned int use_fsb; + + use_fsb = hdata->flags & HPET_DEV_FSB_CAP; + + if (!use_fsb && !is_hpet_wdt_interrupt(hdata)) + return IRQ_NONE; + + if (!(hdata->flags & HPET_DEV_PERI_CAP)) + kick_timer(hdata); + + /* Acknowledge interrupt if in level-triggered mode */ + if (!use_fsb) + hpet_writel(BIT(hdata->num), HPET_STATUS); + + return IRQ_HANDLED; +} + +/** + * setup_irq_msi_mode() - Configure the timer to deliver an MSI interrupt + * @data: Data associated with the instance of the HPET timer to configure + * + * Configure an instance of the HPET timer to deliver interrupts via the Front- + * Side Bus. + * + * Returns: + * + * 0 success. An error code in configuration was unsuccessful. + */ +static int setup_irq_msi_mode(struct hpet_hld_data *hdata) +{ + unsigned int v; + + v = hpet_readl(HPET_Tn_CFG(hdata->num)); + + /* + * If FSB interrupt delivery is used, configure as edge-triggered + * interrupt. We are certain the interrupt comes from the HPET timer as + * we receive the MSI message. + * + * Also, the FSB delivery mode and the FSB route are configured when the + * interrupt is unmasked. + */ + v &= ~HPET_TN_LEVEL; + + hpet_writel(v, HPET_Tn_CFG(hdata->num)); + + return 0; +} + +/** + * setup_irq_legacy_mode() - Configure the timer to deliver an pin interrupt + * @data: Data associated with the instance of the HPET timer to configure + * + * Configure an instance of the HPET timer to deliver interrupts via a pin of + * the IO APIC. + * + * Returns: + * + * 0 success. An error code in configuration was unsuccessful. + */ +static int setup_irq_legacy_mode(struct hpet_hld_data *hdata) +{ + int hwirq = hdata->irq; + unsigned long v; + + v = hpet_readl(HPET_Tn_CFG(hdata->num)); + + v |= hwirq << HPET_TN_ROUTE_SHIFT; + hpet_writel(v, HPET_Tn_CFG(hdata->num)); + + /* + * If IO APIC interrupt delivery is used, configure as level-triggered. + * In this way, the ISR register can be used to determine if this HPET + * timer caused the interrupt at the IO APIC pin. + */ + v |= HPET_TN_LEVEL; + + /* Disable Front-Side Bus delivery. */ + v &= ~HPET_TN_FSB; + + hpet_writel(v, HPET_Tn_CFG(hdata->num)); + + return 0; +} + +/** + * setup_hpet_irq() - Configure the interrupt delivery of an HPET timer + * @data: Data associated with the instance of the HPET timer to configure + * + * Configure the interrupt parameters of an HPET timer. If supported, configure + * interrupts to be delivered via the Front-Side Bus. Also, install an interrupt + * handler. + * + * Returns: + * + * 0 success. An error code in configuration was unsuccessful. + */ +static int setup_hpet_irq(struct hpet_hld_data *hdata) +{ + int hwirq = hdata->irq, ret; + + if (hdata->flags & HPET_DEV_FSB_CAP) + ret = setup_irq_msi_mode(hdata); + else + ret = setup_irq_legacy_mode(hdata); + + if (ret) + return ret; + + /* + * Request an interrupt to activate the irq in all the needed domains. + */ + ret = request_irq(hwirq, hardlockup_detector_irq_handler, + IRQF_TIMER, "hpet_hld", hdata); + + return ret; +} + +/** + * hardlockup_detector_hpet_init() - Initialize the hardlockup detector + * + * Only initialize and configure the detector if an HPET is available on the + * system. + * + * Returns: + * + * 0 success. An error code if initialization was unsuccessful. + */ +static int __init hardlockup_detector_hpet_init(void) +{ + int ret; + + if (!is_hpet_enabled()) + return -ENODEV; + + hld_data = hpet_hardlockup_detector_assign_timer(); + if (!hld_data) + return -ENODEV; + + /* Disable before configuring. */ + disable(hld_data); + + set_periodic(hld_data); + + /* Set timer for the first time relative to the current count. */ + kick_timer(hld_data); + + ret = setup_hpet_irq(hld_data); + if (ret) + return -ENODEV; + + /* + * Timer might have been enabled when the interrupt was unmasked. + * This should be done via the .enable operation. + */ + disable(hld_data); + + return 0; +} diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c40c7b7..6e79833 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -828,6 +828,16 @@ config HARDLOCKUP_DETECTOR_PERF bool select SOFTLOCKUP_DETECTOR +config HARDLOCKUP_DETECTOR_HPET + bool "Use HPET Timer for Hard Lockup Detection" + select SOFTLOCKUP_DETECTOR + select HARDLOCKUP_DETECTOR + depends on HPET_TIMER && HPET + help + Say y to enable a hardlockup detector that is driven by an High-Precision + Event Timer. In addition to selecting this option, the command-line + parameter nmi_watchdog option. See Documentation/admin-guide/kernel-parameters.rst + # # Enables a timestamp based low pass filter to compensate for perf based # hard lockup detection which runs too fast due to turbo modes.