Skip to content

Instantly share code, notes, and snippets.

@bconway
Last active October 29, 2024 14:30
Show Gist options
  • Save bconway/4ce1748461eb28f2b7c68ddb05dba571 to your computer and use it in GitHub Desktop.
Save bconway/4ce1748461eb28f2b7c68ddb05dba571 to your computer and use it in GitHub Desktop.
Detect and mitigate uncontrolled ACPI GPE interrupt storms on OpenBSD (7.3, 7.3-current)
diff --git sys/dev/acpi/acpi.c sys/dev/acpi/acpi.c
index 853bad1ab..26a5c1702 100644
--- sys/dev/acpi/acpi.c
+++ sys/dev/acpi/acpi.c
@@ -52,6 +52,9 @@
#define APMDEV_NORMAL 0
#define APMDEV_CTL 8
+#define GPE_RATE_MIN_CYCLE 5 /* seconds */
+#define GPE_RATE_MAX 1000 /* per second */
+
#include "wd.h"
#ifdef ACPI_DEBUG
@@ -98,6 +101,8 @@ void acpi_disable_allgpes(struct acpi_softc *);
struct gpe_block *acpi_find_gpe(struct acpi_softc *, int);
void acpi_enable_onegpe(struct acpi_softc *, int);
int acpi_gpe(struct acpi_softc *, int, void *);
+void acpi_init_gpe_rate(struct acpi_softc *, int);
+int acpi_gpe_rate(struct acpi_softc *, int);
void acpi_enable_rungpes(struct acpi_softc *);
@@ -2229,6 +2234,7 @@ acpi_enable_onegpe(struct acpi_softc *sc, int gpe)
dnprintf(50, "enabling GPE %.2x (current: %sabled) %.2x\n",
gpe, (en & mask) ? "en" : "dis", en);
acpi_write_pmreg(sc, ACPIREG_GPE_EN, gpe>>3, en | mask);
+ acpi_init_gpe_rate(sc, gpe);
}
/* Clear all GPEs */
@@ -2307,7 +2313,40 @@ acpi_gpe(struct acpi_softc *sc, int gpe, void *arg)
if (sc->gpe_table[gpe].flags & GPE_LEVEL)
acpi_write_pmreg(sc, ACPIREG_GPE_STS, gpe>>3, mask);
en = acpi_read_pmreg(sc, ACPIREG_GPE_EN, gpe>>3);
- acpi_write_pmreg(sc, ACPIREG_GPE_EN, gpe>>3, en | mask);
+ /* Re-enable if GPE rate passes, otherwise leave disabled */
+ if (!acpi_gpe_rate(sc, gpe))
+ acpi_write_pmreg(sc, ACPIREG_GPE_EN, gpe>>3, en | mask);
+ return (0);
+}
+
+void
+acpi_init_gpe_rate(struct acpi_softc *sc, int gpe)
+{
+ sc->gpe_table[gpe].rate_start = getuptime();
+ sc->gpe_table[gpe].rate_count = 0;
+}
+
+int
+acpi_gpe_rate(struct acpi_softc *sc, int gpe)
+{
+ struct gpe_block *pgpe = &sc->gpe_table[gpe];
+ time_t cycle;
+
+ pgpe->rate_count++;
+ dnprintf(10, "rate GPE %.2x start %lld elapsed %lld count %zu\n", gpe,
+ pgpe->rate_start, getuptime() - pgpe->rate_start, pgpe->rate_count);
+
+ cycle = getuptime() - pgpe->rate_start;
+ if (cycle >= GPE_RATE_MIN_CYCLE) {
+ if (pgpe->rate_count > (GPE_RATE_MAX * cycle)) {
+ printf("uncontrolled GPE storm %lld/s, disabling GPE %.2x\n",
+ pgpe->rate_count / cycle, gpe);
+ return (1);
+ }
+
+ /* Reset and start a new cycle */
+ acpi_init_gpe_rate(sc, gpe);
+ }
return (0);
}
diff --git sys/dev/acpi/acpivar.h sys/dev/acpi/acpivar.h
index a9b4a2ae9..4e2f47053 100644
--- sys/dev/acpi/acpivar.h
+++ sys/dev/acpi/acpivar.h
@@ -185,6 +185,9 @@ struct gpe_block {
void *arg;
int active;
int flags;
+
+ time_t rate_start;
+ size_t rate_count;
};
struct acpi_devlist {
@joemiller
Copy link

@bconway I tried this out on 7.2-stable branch and it is working well. thank you

@bconway
Copy link
Author

bconway commented Mar 30, 2023

Verified on OpenBSD 7.3.

@withs
Copy link

withs commented Jan 10, 2024

Applied and tested on 7.4 -stable, it work great thanks.

@bconway
Copy link
Author

bconway commented Jan 10, 2024

Thanks! I'm no longer using this diff, as the one system I had afflicted was a cured with an (unofficial) BIOS update, but I will come back to it in the future if the occasion arises.

@qfen
Copy link

qfen commented Aug 21, 2024

Works on 7.5-stable. On my (similar?) fanless system, idle CPU temp dropped by 6C by about 15 minutes after rebooting into the modified kernel.

@withs
Copy link

withs commented Oct 29, 2024

looks like this is still working on obsd 7.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment