[PATCH v7 15/24] x86/hpet: Reserve an HPET channel for the hardlockup detector

From: Ricardo Neri
Date: Wed Mar 01 2023 - 18:38:49 EST


Create a new HPET_MODE_NMI_WATCHDOG mode category to reserve an HPET
channel for the hard lockup detector.

Only reserve the channel if the HPET frequency is sufficiently low to allow
32-bit register accesses and if Front Side BUS interrupt delivery (i.e.,
MSI interrupts) is supported.

Cc: Andi Kleen <ak@xxxxxxxxxxxxxxx>
Cc: Stephane Eranian <eranian@xxxxxxxxxx>
Cc: "Ravi V. Shankar" <ravi.v.shankar@xxxxxxxxx>
Cc: iommu@xxxxxxxxxxxxxxxxxxxxxxxxxx
Cc: linuxppc-dev@xxxxxxxxxxxxxxxx
Reviewed-by: Tony Luck <tony.luck@xxxxxxxxx>
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@xxxxxxxxxxxxxxx>
---
Changes since v6:
* Reworded the commit message for clarity.
* Removed pointless global variable hld_data.

Changes since v5:
* Added a check for the allowed maximum frequency of the HPET.
* Added hpet_hld_free_timer() to properly free the reserved HPET channel
if the initialization is not completed.
* Call hpet_assign_irq() with as_nmi = true.
* Relocated declarations of functions and data structures of the detector
to not depend on CONFIG_HPET_TIMER.

Changes since v4:
* Reworked timer reservation to use Thomas' rework on HPET channel
management.
* Removed hard-coded channel number for the hardlockup detector.
* Provided more details on the sequence of HPET channel reservations.
(Thomas Gleixner)
* Only reserve a channel for the hardlockup detector if enabled via
kernel command line. The function reserving the channel is called from
hardlockup detector. (Thomas Gleixner)
* Shorten the name of hpet_hardlockup_detector_get_timer() to
hpet_hld_get_timer(). (Andi)
* Simplify error handling when a channel is not found. (Tony)

Changes since v3:
* None

Changes since v2:
* None

Changes since v1:
* None
---
arch/x86/include/asm/hpet.h | 22 ++++++++
arch/x86/kernel/hpet.c | 100 ++++++++++++++++++++++++++++++++++++
2 files changed, 122 insertions(+)

diff --git a/arch/x86/include/asm/hpet.h b/arch/x86/include/asm/hpet.h
index 486e001413c7..5762bd0169a1 100644
--- a/arch/x86/include/asm/hpet.h
+++ b/arch/x86/include/asm/hpet.h
@@ -103,4 +103,26 @@ static inline int is_hpet_enabled(void) { return 0; }
#define default_setup_hpet_msi NULL

#endif
+
+#ifdef CONFIG_X86_HARDLOCKUP_DETECTOR_HPET
+/**
+ * struct hpet_hld_data - Data needed to operate the detector
+ * @has_periodic: The HPET channel supports periodic mode
+ * @channel: HPET channel assigned to the detector
+ * @channe_priv: Private data of the assigned channel
+ * @ticks_per_second: Frequency of the HPET timer
+ * @irq: IRQ number assigned to the HPET channel
+ */
+struct hpet_hld_data {
+ bool has_periodic;
+ u32 channel;
+ struct hpet_channel *channel_priv;
+ u64 ticks_per_second;
+ int irq;
+};
+
+extern struct hpet_hld_data *hpet_hld_get_timer(void);
+extern void hpet_hld_free_timer(struct hpet_hld_data *hdata);
+#endif /* CONFIG_X86_HARDLOCKUP_DETECTOR_HPET */
+
#endif /* _ASM_X86_HPET_H */
diff --git a/arch/x86/kernel/hpet.c b/arch/x86/kernel/hpet.c
index f42ce3fc4528..97570426f324 100644
--- a/arch/x86/kernel/hpet.c
+++ b/arch/x86/kernel/hpet.c
@@ -20,6 +20,7 @@ enum hpet_mode {
HPET_MODE_LEGACY,
HPET_MODE_CLOCKEVT,
HPET_MODE_DEVICE,
+ HPET_MODE_NMI_WATCHDOG,
};

struct hpet_channel {
@@ -216,6 +217,7 @@ static void __init hpet_reserve_platform_timers(void)
break;
case HPET_MODE_CLOCKEVT:
case HPET_MODE_LEGACY:
+ case HPET_MODE_NMI_WATCHDOG:
hpet_reserve_timer(&hd, hc->num);
break;
}
@@ -1498,3 +1500,101 @@ irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id)
}
EXPORT_SYMBOL_GPL(hpet_rtc_interrupt);
#endif
+
+#ifdef CONFIG_X86_HARDLOCKUP_DETECTOR_HPET
+
+/*
+ * We program the channel in 32-bit mode to reduce the number of register
+ * accesses. The maximum value of watch_thresh is 60 seconds. The HPET counter
+ * should not wrap around more frequently than that: its frequency must be less
+ * than 71.582788 MHz. For safety, limit the frequency to 85% of the maximum
+ * permitted frequency.
+ *
+ * The frequency of the HPET in most systems in the field is less than 24MHz.
+ */
+#define HPET_HLD_MAX_FREQ 60845000ULL
+
+/**
+ * hpet_hld_free_timer - Free the reserved channel for the hardlockup detector
+ * @hdata: Data structure representing the reserved channel.
+ *
+ * Returns: none
+ */
+void hpet_hld_free_timer(struct hpet_hld_data *hld_data)
+{
+ hld_data->channel_priv->mode = HPET_MODE_UNUSED;
+ hld_data->channel_priv->in_use = 0;
+ kfree(hld_data);
+}
+
+/**
+ * hpet_hld_get_timer - Get an HPET channel for the hardlockup detector
+ *
+ * Reserve an HPET channel if available, supports FSB mode, and has sufficiently
+ * low frequency. This function is called by the hardlockup detector if enabled
+ * in the kernel command line.
+ *
+ * Returns: a pointer with the properties of the reserved HPET channel.
+ */
+struct hpet_hld_data *hpet_hld_get_timer(void)
+{
+ struct hpet_channel *hc = hpet_base.channels;
+ struct hpet_hld_data *hld_data;
+ int i, irq;
+
+ if (hpet_freq > HPET_HLD_MAX_FREQ)
+ return NULL;
+
+ for (i = 0; i < hpet_base.nr_channels; i++) {
+ hc = hpet_base.channels + i;
+
+ /*
+ * Associate the first unused channel to the hardlockup
+ * detector. Bailout if we cannot find one. This may happen if
+ * the HPET clocksource has taken all the timers. The HPET
+ * driver (/dev/hpet) has not taken any channels at this point.
+ */
+ if (hc->mode == HPET_MODE_UNUSED)
+ break;
+ }
+
+ if (i == hpet_base.nr_channels)
+ return NULL;
+
+ if (!(hc->boot_cfg & HPET_TN_FSB_CAP))
+ return NULL;
+
+ hld_data = kzalloc(sizeof(*hld_data), GFP_KERNEL);
+ if (!hld_data)
+ return NULL;
+
+ hc->mode = HPET_MODE_NMI_WATCHDOG;
+ hc->in_use = 1;
+ hld_data->channel_priv = hc;
+
+ if (hc->boot_cfg & HPET_TN_PERIODIC_CAP)
+ hld_data->has_periodic = true;
+
+ if (!hpet_domain)
+ hpet_domain = hpet_create_irq_domain(hpet_blockid);
+
+ if (!hpet_domain)
+ goto err;
+
+ /* Assign an IRQ with NMI delivery mode. */
+ irq = hpet_assign_irq(hpet_domain, hc, hc->num, true);
+ if (irq <= 0)
+ goto err;
+
+ hc->irq = irq;
+ hld_data->irq = irq;
+ hld_data->channel = i;
+ hld_data->ticks_per_second = hpet_freq;
+
+ return hld_data;
+
+err:
+ hpet_hld_free_timer(hld_data);
+ return NULL;
+}
+#endif /* CONFIG_X86_HARDLOCKUP_DETECTOR_HPET */
--
2.25.1