diff --git a/include/dev/acpi.h b/include/dev/acpi.h index 0a28a56..285cba2 100644 --- a/include/dev/acpi.h +++ b/include/dev/acpi.h @@ -30,4 +30,5 @@ void acpi_thread_start(void); void acpi_pci_route(void); int acpi_poweroff(void); // S5; returns only on failure +void acpi_poweroff_now(void); // S5 from a panic, no AML; returns only on failure int acpi_reboot(void); // the FADT reset register; ditto diff --git a/kernel/acpi/aml.c b/kernel/acpi/aml.c index c4b894b..cc687ab 100644 --- a/kernel/acpi/aml.c +++ b/kernel/acpi/aml.c @@ -32,9 +32,14 @@ #include #include #include +#include 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) { diff --git a/kernel/acpi/i2c_hid.c b/kernel/acpi/i2c_hid.c index e418e1a..e591fd3 100644 --- a/kernel/acpi/i2c_hid.c +++ b/kernel/acpi/i2c_hid.c @@ -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) { diff --git a/kernel/core/panic.c b/kernel/core/panic.c index 7117cc1..b29939c 100644 --- a/kernel/core/panic.c +++ b/kernel/core/panic.c @@ -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; } }