i2c-hid on a stack of its own; PSOGM powers off after three minutes

The touchpad driver crashed the ASUS TUF A15 at boot. Its thread runs the
firmware's AML -- _DSM, _PS0, _CRS -- through uACPI at -O0, on an 8 KB
kernel stack, and kernel stacks are one array: running off the end lands
in the next thread's stack, and the machine dies a moment later in some
other code. The thread now runs on a 64 KB stack of its own, through
SCFF's call_on_stack().

And a crashed laptop sat with its fans up until somebody held the power
button, since the other processors keep running. On an ordinary boot the
PSOGM now says it will power off, and does, after three minutes: SLP_TYP
from \_S5, read at boot, written straight to PM1 control with no AML, which
could sleep or wait on a lock a dead processor holds. A verbose boot keeps
the screen up for ever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 00:21:39 +02:00
commit b15fb2fdaa
4 changed files with 85 additions and 3 deletions

View file

@ -32,9 +32,14 @@
#include <uacpi/resources.h>
#include <uacpi/event.h>
#include <uacpi/sleep.h>
#include <uacpi/registers.h>
static int ready;
// \_S5's SLP_TYP values, read once at boot for acpi_poweroff_now(): -1 until
// then, or if the firmware has none.
static int s5_typ_a = -1, s5_typ_b = -1;
static uacpi_interrupt_ret power_button(uacpi_handle ctx);
void
@ -74,6 +79,23 @@ acpi_aml_init(void)
kinfo("acpi: no fixed power button: %s\n", uacpi_status_to_string(st));
uacpi_finalize_gpe_initialization();
{
uacpi_object *o = 0;
uacpi_object_array p;
uacpi_u64 ta, tb;
if (uacpi_eval_package(0, "\\_S5", 0, &o) == UACPI_STATUS_OK) {
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK && p.count >= 1 &&
uacpi_object_get_integer(p.objects[0], &ta) == UACPI_STATUS_OK) {
tb = ta;
if (p.count >= 2)
uacpi_object_get_integer(p.objects[1], &tb);
s5_typ_a = ta & 7;
s5_typ_b = tb & 7;
}
uacpi_object_unref(o);
}
}
ready = 1;
kinfo("acpi: namespace loaded\n");
}
@ -118,6 +140,28 @@ acpi_poweroff(void)
return -1;
}
// Power off from a dead kernel: the PSOGM, after it has been up long enough to
// be read. acpi_poweroff() runs _PTS and other AML, which can sleep and take
// locks that another processor died holding, so it is no use here. This is
// only the last step of it -- SLP_TYP from \_S5, then SLP_EN, written straight
// to the PM1 control registers. The firmware's preparations are skipped; a
// crashed machine has nothing to save. Returns only if the machine is still on.
void
acpi_poweroff_now(void)
{
uacpi_u64 cnt = 0, a, b;
if (!ready || s5_typ_a < 0)
return;
uacpi_read_register(UACPI_REGISTER_PM1_CNT, &cnt);
cnt &= ~(uacpi_u64)((7 << 10) | (1 << 13));
a = cnt | ((uacpi_u64)s5_typ_a << 10);
b = cnt | ((uacpi_u64)s5_typ_b << 10);
uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a, b);
uacpi_write_registers(UACPI_REGISTER_PM1_CNT, a | (1 << 13), b | (1 << 13));
microdelay(100000);
}
int
acpi_reboot(void)
{

View file

@ -612,12 +612,25 @@ probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
return UACPI_ITERATION_DECISION_CONTINUE;
}
static void
i2c_hid_thread(void)
// **The thread runs on a stack of its own, 64 KB.** A kernel thread's stack
// is 8 KB, and this one runs the firmware's AML -- _DSM, _PS0, _CRS, and
// whatever those call -- through uACPI's interpreter, built at -O0. On the
// ASUS TUF A15 that ran off the end: kernel stacks are one array, so it ran
// into the next thread's, and the machine died a moment later in whatever
// that thread was doing, with a crash report about the wrong code.
// call_on_stack() is SCFF's (kernel/scff/scff.c). Sleeping here is fine:
// the context is saved on this stack, and only this thread ever uses it.
static unsigned char i2c_stack[64 * 1024] __attribute__((aligned(16)));
long call_on_stack(long (*fn)(void *), void *arg, void *top);
static long
i2c_hid_main(void *arg)
{
static const char *const ids[] = { "PNP0C50", "ACPI0C50", 0 };
int i, n;
(void)arg;
STEP("i2c-hid: looking for touchpads\n");
uacpi_find_devices_at(uacpi_namespace_root(), ids, probe, 0);
STEP("i2c-hid: %d found\n", ndev);
@ -642,6 +655,12 @@ i2c_hid_thread(void)
}
}
static void
i2c_hid_thread(void)
{
call_on_stack(i2c_hid_main, 0, i2c_stack + sizeof(i2c_stack));
}
void
i2c_hid_start(void)
{

View file

@ -45,6 +45,7 @@
#include "gfx/theme.h"
#include "gfx/logo.h" /* LOGO_H -- the mark's height, for the layout below */
#include "gfx/qr.h"
#include "dev/acpi.h"
extern uint ticks;
extern int panicked;
@ -501,8 +502,12 @@ paint(char *reason, addr_t *pcs, int npcs)
// 62-character command line this replaces used to run underneath it.
text_at(left, sh - ch * 4,
"This report is in /proc/kmsg after a restart.", PSOGM_DIM);
// A verbose boot keeps the screen up for ever; any other powers off after
// PANIC_OFF_MS (see panic_blink), and says so rather than surprising anyone.
text_at(left, sh - ch * 4 + ch + 4,
"schdbg, or Ctrl-G on the console, asks more.", PSOGM_DIM);
boot_verbose ? "schdbg, or Ctrl-G on the console, asks more."
: "The machine powers itself off in 3 minutes.",
PSOGM_DIM);
text_at(left, sh - ch * 4 + (ch + 4) * 2,
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
gfx_flush_all();
@ -634,12 +639,24 @@ kbd_drain_ack(void)
// Scroll, Num and Caps, which is bits 0, 1 and 2 of the byte after 0xED.
#define LED_ALL 0x07
// How long the report stays up before the machine powers itself off, on an
// ordinary boot. A crashed laptop otherwise sits with its fans up -- the other
// processors are still running -- until somebody holds the power button, and
// three minutes is long enough to photograph the screen or scan the QR code.
// A verbose boot is somebody debugging on purpose, and keeps it up for ever.
#define PANIC_OFF_MS (3 * 60 * 1000)
static void
panic_blink(void)
{
int on = 0;
uint elapsed = 0;
for (;;) {
if (!boot_verbose && elapsed >= PANIC_OFF_MS) {
acpi_poweroff_now();
elapsed = 0; // still on: blink on, and try again later
}
kbd_out(0xED);
kbd_drain_ack();
kbd_out(on ? LED_ALL : 0x00);
@ -655,5 +672,6 @@ panic_blink(void)
// kernel that died during boot may be missing.
for (int i = 0; i < 300; i++)
microdelay(1000);
elapsed += 300;
}
}