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 {
@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