# GPE Storm
<!--
Break down things by observe -> exaplin what it is -> how does that cause the problem -> how to solve it
-->
One may find his/her Linux machine always has 1 CPU under full load. In system monitor such as `top` or `htop`, a command called `kworker` takes up nearly 100% of a CPU.
## Observation
This problem mostly happens on laptops. On my MSI GS60 6QD, with kernel `Linux 6.8.7-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 17 Apr 2024 15:20:28 +0000 x86_64`, top two CPU usage listed in `top` are `kworker/0:1+kacpid` and `irq/9-acpi `
```
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
10 root 20 0 0 0 0 R 82.1 0.0 9:32.66 kworker/0:1+kacpid
79 root -51 0 0 0 0 R 15.6 0.0 1:48.09 irq/9-acpi
```
`kworker/0:1+kacpid` constantly takes high CPU usage, and `irq/9-acpi` constantly takes around 15% as well. This drains the battery fast and drives CPU fans to high load. It happens right from booting up.
## Investigation
kworker refers to worker threads managed in worker-pool that consume work items in workqueue ([CMWQ](https://docs.kernel.org/core-api/workqueue.html)). Subsystems and drivers can create and queue work items through workqueue API functions as they see fit. The number after `kworker/` denotes the CPU core (in this case, core 0) and the specific worker id. These numbers are determined when `create_worker()` is called (defined in `kernel/workqueue.c`). `kacpid` stands for Kernel ACPI Daemon. [ACPI](https://en.wikipedia.org/wiki/ACPI) is a standard for handling power management and hardware configuration. The kacpid process is responsible for ACPI-related tasks such as managing power events and handling ACPI interrupts.
`irq/9-acpi` indicates that it's an interrupt request associated with IRQ line 9 and ACPI. In [x86 IRQ](https://en.wikipedia.org/wiki/Interrupt_request#x86_IRQs), IRQ 9 is on Slave PIC, for ACPI interrupts on Intel Chipsets.
The handling of interrupts has [two parts](https://0xax.gitbooks.io/linux-insides/content/Interrupts/linux-interrupts-9.html): top half and bottom half. Top half, which receives the hardware interrupt, needs to run as quickly as possible. Bottom half, where corresponding tasks to the interrupt are executed, is not as time-critical in non-real-time system and can be deferred for later execution. There are several options for this deferral: softirq, tasklet, workqueue and threaded interrupts. In this case, ACPI uses workqueue to do so.
So far we can figure out that my computer is probably handling excessive numbers ACPI interrupts. We have two options:
1. Fix the hardware and firmware. Stop it from sending insane number of interrupts.
2. Stop handling the interrupts.
## Possible Solutions
Stop handling the interrupts seems keep a fairly easy option. UEFI firmware on my laptop is closed-source software. Open-source ones such as Core Boot don't support my laptop, either. [This document](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-firmware-acpi) in kernel explains how to use [sysfs](https://en.wikipedia.org/wiki/Sysfs) to check ACPI firmware behavior, including interrupts.
> However, one of the main functions of ACPI is to make the platform understand random hardware without special driver support. So while the SCI handles a few well known (fixed feature) interrupts sources, such as the power button, it can also handle a variable number of a "General Purpose Events" (GPE).
>
> A GPE vectors to a specified handler in AML, which can do a anything the BIOS writer wants from OS context. GPE 0x12, for example, would vector to a level or edge handler called \_L12 or \_E12. The handler may do its business and return. Or the handler may send send a Notify event to a Linux device driver registered on an ACPI device, such as a battery, or a processor.
>
> To figure out where all the SCI's are coming from, /sys/firmware/acpi/interrupts contains a file listing every possible source, and the count of how many times it has triggered
> Besides this, user can also write specific strings to these files to enable/disable/clear ACPI interrupts in user space, which can be used to debug some ACPI interrupt storm issues.
As suggested, use `grep . -r /sys/firmware/acpi/interrupts/` to list possible sources and triggered count:
```c
error: 0
ff_gbl_lock: 0 disabled unmasked
ff_pmtimer: 0 STS invalid unmasked
ff_pwr_btn: 0 EN enabled unmasked
ff_rt_clk: 0 disabled unmasked
ff_slp_btn: 0 invalid unmasked
gpe00: 0 invalid unmasked
gpe0A: 0 invalid unmasked
gpe0B: 0 invalid unmasked
gpe0C: 0 EN enabled unmasked
gpe0D: 0 invalid unmasked
gpe0E: 0 invalid unmasked
gpe0F: 0 invalid unmasked
gpe01: 0 invalid unmasked
...
...
...
gpe58: 0 invalid unmasked
gpe59: 0 invalid unmasked
gpe60: 0 invalid unmasked
gpe61: 2461007 STS enabled unmasked
gpe62: 0 EN enabled unmasked
gpe63: 0 invalid unmasked
gpe64: 0 invalid unmasked
gpe65: 0 invalid unmasked
gpe66: 9 EN enabled unmasked
gpe67: 0 enabled unmasked
gpe68: 0 invalid unmasked
gpe69: 0 disabled unmasked
gpe70: 0 invalid unmasked
gpe71: 0 invalid unmasked
gpe72: 0 invalid unmasked
gpe73: 0 invalid unmasked
gpe74: 0 invalid unmasked
gpe75: 0 invalid unmasked
gpe76: 0 invalid unmasked
gpe77: 0 invalid unmasked
gpe78: 0 invalid unmasked
gpe79: 0 invalid unmasked
gpe_all: 2461163
sci: 2461131
sci_not: 4
```
As listed, GPE61 is triggered 2461007 times in only a few minutes. Disable GPE61 by `echo disable > /sys/firmware/acpi/interrupts/gpe61`. To disable it everytime system booted, add as a cron job in root user.
One may think how about disable the interrupt right at [PIC](https://en.wikipedia.org/wiki/Programmable_interrupt_controller)? This may disable all the other normal ACPI interrupts.
Along the above investigation, this could be the stepping stone of exploring interrupt mechanism in Linux kernel.
## Further investigation
* How about masking it instead of disabling it?
* Use `tools/workqueue/wq_monitor.py` or `tools/workqueue/wq_dump.py` to examine what GPE61 on my laptop really does. Find if disabling it poses any impact on the system. Not sure if my battery died pretty quick because of this.
* [This post](https://askubuntu.com/questions/148726/what-is-an-acpi-gpe-storm) suggest "Transactions will use polling mode means that to handle the storm, the OS will stop using GPEs/interrupts to be informed of ACPI events and will instead -- on its own schedule -- "poll" or proactively ask the ACPI EC if any events it should know of have occurred. This way, the OS can still effectively perform ACPI functions while not being overwhelmed with a GPE storm."
* Dig further into how interrupt works by following [this Lab](https://linux-kernel-labs.github.io/refs/heads/master/labs/interrupts.html).
* A comment in a [LWN article](https://lwn.net/Articles/355700/) introducing Concurrency-managed workqueues:
> Article:
> ...The ACPI code had bound a workqueue thread to CPU 0 because some operations corrupt the system if run anywhere else; ...
>
> Comment:
> (reply to someone being shocked) On some HPs, at least, certain ACPI operations trigger SMIs that then appear to be run on the CPU that triggered the SMI. HP's SMI handler seems to fail to restore CPU state if it runs on anything other than CPU 0.
* This fits the observation that kworker works on CPU 0.
* Notice that "disable interrupt" in the top half is different from this one we are doing here. Disabling interrupts in the top half is stop receiving all the other interrupts, which is a key latency concern for Linux to be hard-real-time.