From df27cc87b5abb1d6974ee85f5d09e0e8dcb3e7b2 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Mon, 5 Dec 2022 15:25:12 +0100 Subject: [PATCH] hw/x86: add suspend kernel syscall using the ACPI mechanism. The syscall can be triggered solely via core's RPC managing_system call. Issue #4669 --- repos/base-hw/include/kernel/interface.h | 16 +++ .../base-hw/lib/mk/spec/x86_64/core-hw-pc.mk | 4 + repos/base-hw/src/core/kernel/thread.cc | 1 + repos/base-hw/src/core/kernel/thread.h | 1 + repos/base-hw/src/core/platform.h | 6 + .../src/core/spec/arm/kernel/thread.cc | 3 + .../src/core/spec/arm_v8/kernel/thread.cc | 3 + .../src/core/spec/riscv/kernel/thread.cc | 3 + .../src/core/spec/x86_64/kernel/thread.cc | 132 +++++++++++++++++- .../core/spec/x86_64/pd_session_support.cc | 60 ++++++++ repos/libports/run/acpi_suspend.run | 9 +- 11 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 repos/base-hw/src/core/spec/x86_64/pd_session_support.cc diff --git a/repos/base-hw/include/kernel/interface.h b/repos/base-hw/include/kernel/interface.h index f636b60075..0925f0c6dc 100644 --- a/repos/base-hw/include/kernel/interface.h +++ b/repos/base-hw/include/kernel/interface.h @@ -46,6 +46,7 @@ namespace Kernel { constexpr Call_arg call_id_time() { return 21; } constexpr Call_arg call_id_run_vm() { return 22; } constexpr Call_arg call_id_pause_vm() { return 23; } + constexpr Call_arg call_id_suspend() { return 24; } /***************************************************************** @@ -431,6 +432,21 @@ namespace Kernel { { call(call_id_pause_vm(), cap); } + + + /** + * Suspend hardware + * + * \param sleep_type The intended sleep state S0 ... S5. The values are + * read out by an ACPI AML component and are of type + * TYP_SLPx as described in the ACPI specification, + * e.g. TYP_SLPa and TYP_SLPb. The values differ + * between different PC systems/boards. + */ + inline bool suspend(unsigned const sleep_type) + { + return bool(call(call_id_suspend(), sleep_type)); + } } #endif /* _INCLUDE__KERNEL__INTERFACE_H_ */ diff --git a/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk b/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk index 318083f6f9..bf44809ada 100644 --- a/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk +++ b/repos/base-hw/lib/mk/spec/x86_64/core-hw-pc.mk @@ -37,6 +37,10 @@ SRC_CC += spec/x86_64/platform_support_common.cc SRC_CC += spec/64bit/memory_map.cc +PD_SESSION_SUPPORT_CC_PATH := \ + $(call select_from_repositories,src/core/spec/x86_64/pd_session_support.cc) + +vpath pd_session_support.cc $(dir $(PD_SESSION_SUPPORT_CC_PATH)) vpath spec/64bit/memory_map.cc $(call select_from_repositories,src/lib/hw) # include less specific configuration diff --git a/repos/base-hw/src/core/kernel/thread.cc b/repos/base-hw/src/core/kernel/thread.cc index 4b375ab691..b11ce7deca 100644 --- a/repos/base-hw/src/core/kernel/thread.cc +++ b/repos/base-hw/src/core/kernel/thread.cc @@ -870,6 +870,7 @@ void Thread::_call() case call_id_ack_irq(): _call_ack_irq(); return; case call_id_new_obj(): _call_new_obj(); return; case call_id_delete_obj(): _call_delete_obj(); return; + case call_id_suspend(): _call_suspend(); return; default: Genode::raw(*this, ": unknown kernel call"); _die(); diff --git a/repos/base-hw/src/core/kernel/thread.h b/repos/base-hw/src/core/kernel/thread.h index 926f434db4..604deba6bd 100644 --- a/repos/base-hw/src/core/kernel/thread.h +++ b/repos/base-hw/src/core/kernel/thread.h @@ -295,6 +295,7 @@ class Kernel::Thread : private Kernel::Object, public Cpu_job, private Timeout void _call_timeout(); void _call_timeout_max_us(); void _call_time(); + void _call_suspend(); template void _call_new(ARGS &&... args) diff --git a/repos/base-hw/src/core/platform.h b/repos/base-hw/src/core/platform.h index 8924242887..acbf059d0e 100644 --- a/repos/base-hw/src/core/platform.h +++ b/repos/base-hw/src/core/platform.h @@ -150,6 +150,12 @@ class Genode::Platform : public Genode::Platform_generic size_t max_caps() const override { return Kernel::Pd::max_cap_ids; } static addr_t core_main_thread_phys_utcb(); + + template + static void apply_with_boot_info(T const &fn) + { + fn(_boot_info()); + } }; #endif /* _CORE__PLATFORM_H_ */ diff --git a/repos/base-hw/src/core/spec/arm/kernel/thread.cc b/repos/base-hw/src/core/spec/arm/kernel/thread.cc index 0a63aac23b..9628aedff3 100644 --- a/repos/base-hw/src/core/spec/arm/kernel/thread.cc +++ b/repos/base-hw/src/core/spec/arm/kernel/thread.cc @@ -26,6 +26,9 @@ using namespace Kernel; extern "C" void kernel_to_user_context_switch(Cpu::Context*, Cpu::Fpu_context*); +void Thread::_call_suspend() { } + + void Thread::exception(Cpu & cpu) { switch (regs->cpu_exception) { diff --git a/repos/base-hw/src/core/spec/arm_v8/kernel/thread.cc b/repos/base-hw/src/core/spec/arm_v8/kernel/thread.cc index 34946b1183..7a6486a64d 100644 --- a/repos/base-hw/src/core/spec/arm_v8/kernel/thread.cc +++ b/repos/base-hw/src/core/spec/arm_v8/kernel/thread.cc @@ -24,6 +24,9 @@ extern "C" void kernel_to_user_context_switch(void *, void *); using namespace Kernel; +void Thread::_call_suspend() { } + + void Thread::exception(Cpu & cpu) { switch (regs->exception_type) { diff --git a/repos/base-hw/src/core/spec/riscv/kernel/thread.cc b/repos/base-hw/src/core/spec/riscv/kernel/thread.cc index 6ab3c3004d..4f95ff17e7 100644 --- a/repos/base-hw/src/core/spec/riscv/kernel/thread.cc +++ b/repos/base-hw/src/core/spec/riscv/kernel/thread.cc @@ -95,6 +95,9 @@ void Thread::exception(Cpu & cpu) } +void Thread::_call_suspend() { } + + void Thread::_call_cache_coherent_region() { } diff --git a/repos/base-hw/src/core/spec/x86_64/kernel/thread.cc b/repos/base-hw/src/core/spec/x86_64/kernel/thread.cc index 3ed9b75986..3d0c2c61ea 100644 --- a/repos/base-hw/src/core/spec/x86_64/kernel/thread.cc +++ b/repos/base-hw/src/core/spec/x86_64/kernel/thread.cc @@ -7,7 +7,7 @@ */ /* - * Copyright (C) 2013-2017 Genode Labs GmbH + * Copyright (C) 2013-2023 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -18,6 +18,11 @@ #include #include +#include +#include + +#include + void Kernel::Thread::Tlb_invalidation::execute(Cpu &) { @@ -29,13 +34,132 @@ void Kernel::Thread::Tlb_invalidation::execute(Cpu &) global_work_list.remove(&_le); caller._restart(); } -}; +} -void Kernel::Thread::Flush_and_stop_cpu::execute(Cpu &) { } +void Kernel::Thread::Flush_and_stop_cpu::execute(Cpu &cpu) +{ + if (--cpus_left == 0) { + /* last CPU triggers final ACPI suspend outside kernel lock */ + cpu.suspend.typ_a = suspend.typ_a; + cpu.suspend.typ_b = suspend.typ_b; + cpu.next_state_suspend(); + return; + } + + /* halt CPU outside kernel lock */ + cpu.next_state_halt(); + + /* adhere to ACPI specification */ + asm volatile ("wbinvd" : : : "memory"); +} -void Kernel::Cpu::Halt_job::proceed(Kernel::Cpu &) { } +void Kernel::Cpu::Halt_job::Halt_job::proceed(Kernel::Cpu &cpu) +{ + switch (cpu.state()) { + case HALT: + while (true) { + asm volatile ("hlt"); } + break; + case SUSPEND: + using Genode::Platform; + + Platform::apply_with_boot_info([&](auto const &boot_info) { + auto table = boot_info.plat_info.acpi_fadt; + auto acpi_fadt_table = reinterpret_cast(Platform::mmio_to_virt(table)); + + /* paranoia */ + if (!acpi_fadt_table) + return; + + /* all CPUs signaled that they are stopped, trigger ACPI suspend */ + Hw::Acpi_fadt fadt(acpi_fadt_table); + + /* ack all GPEs, otherwise we may wakeup immediately */ + fadt.clear_gpe0_status(); + fadt.clear_gpe1_status(); + + /* adhere to ACPI specification */ + asm volatile ("wbinvd" : : : "memory"); + + fadt.suspend(cpu.suspend.typ_a, cpu.suspend.typ_b); + + Genode::raw("kernel: unexpected resume"); + }); + break; + default: + break; + } + + Genode::raw("unknown cpu state"); + while (true) { + asm volatile ("hlt"); + } +} + + +void Kernel::Thread::_call_suspend() +{ + using Genode::uint8_t; + using Genode::Platform; + + Hw::Acpi_generic * acpi_fadt_table { }; + unsigned cpu_count { }; + + Platform::apply_with_boot_info([&](auto const &boot_info) { + auto table = boot_info.plat_info.acpi_fadt; + if (table) + acpi_fadt_table = reinterpret_cast(Platform::mmio_to_virt(table)); + + cpu_count = boot_info.cpus; + }); + + if (!acpi_fadt_table || !cpu_count) { + user_arg_0(0 /* fail */); + return; + } + + if (_stop_cpu.constructed()) { + if (_stop_cpu->cpus_left) { + Genode::raw("kernel: resume still ongoing"); + user_arg_0(0 /* fail */); + return; + } + + /* remove & destruct Flush_and_stop_cpu object */ + _stop_cpu.destruct(); + user_arg_0(1 /* success */); + + return; + } + + auto const sleep_typ_a = uint8_t(user_arg_1()); + auto const sleep_typ_b = uint8_t(user_arg_1() >> 8); + + _stop_cpu.construct(_cpu_pool.work_list(), cpu_count - 1, + Hw::Suspend_type { sleep_typ_a, sleep_typ_b }); + + /* single core CPU case */ + if (cpu_count == 1) { + auto &cpu = _cpu_pool.executing_cpu(); + /* this CPU triggers final ACPI suspend outside kernel lock */ + cpu.next_state_suspend(); + return; + } + + /* trigger IPIs to all beside current CPU */ + _cpu_pool.for_each_cpu([&] (Cpu &cpu) { + + if (cpu.id() == Cpu::executing_id()) { + /* halt CPU outside kernel lock */ + cpu.next_state_halt(); + return; + } + + cpu.trigger_ip_interrupt(); + }); +} void Kernel::Thread::_call_cache_coherent_region() { } diff --git a/repos/base-hw/src/core/spec/x86_64/pd_session_support.cc b/repos/base-hw/src/core/spec/x86_64/pd_session_support.cc new file mode 100644 index 0000000000..d3b32ff266 --- /dev/null +++ b/repos/base-hw/src/core/spec/x86_64/pd_session_support.cc @@ -0,0 +1,60 @@ +/* + * \brief Core implementation of the PD session interface + * \author Alexander Boettcher + * \date 2022-12-02 + */ + +/* + * Copyright (C) 2022 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include +#include + +using namespace Genode; +using State = Genode::Pd_session::Managing_system_state; + + +State Pd_session_component::managing_system(State const &request) +{ + bool const suspend = (_managing_system == Managing_system::PERMITTED) && + (request.trapno == State::ACPI_SUSPEND_REQUEST); + State respond { }; + + if (!suspend) { + /* report failed attempt */ + respond.trapno = 0; + return respond; + } + + /* + * The trapno/ip/sp registers used below are just convention to transfer + * the intended sleep state S0 ... S5. The values are read out by an + * ACPI AML component and are of type TYP_SLPx as described in the + * ACPI specification, e.g. TYP_SLPa and TYP_SLPb. The values differ + * between different PC systems/boards. + * + * \note trapno/ip/sp registers are chosen because they exist in + * Managing_system_state for x86_32 and x86_64. + */ + unsigned const sleep_type_a = request.ip & 0xffu; + unsigned const sleep_type_b = request.sp & 0xffu; + + respond.trapno = Kernel::suspend((sleep_type_b << 8) | sleep_type_a); + + return respond; +} + + +/*************************** + ** Dummy implementations ** + ***************************/ + +bool Pd_session_component::assign_pci(addr_t, uint16_t) { return true; } + + +void Pd_session_component::map(addr_t, addr_t) { } + diff --git a/repos/libports/run/acpi_suspend.run b/repos/libports/run/acpi_suspend.run index 8bd59aaa95..2f04f2e122 100644 --- a/repos/libports/run/acpi_suspend.run +++ b/repos/libports/run/acpi_suspend.run @@ -10,7 +10,14 @@ # assert_spec x86 -assert_spec nova + +if { + ![have_spec hw] && + ![have_spec nova] +} { + puts "Platform is unsupported." + exit 0 +} # non Intel machines has no GPU support, e.g. Qemu and AMD set board_non_intel [expr [have_include "power_on/qemu"]]