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
This commit is contained in:
Alexander Boettcher 2022-12-05 15:25:12 +01:00 committed by Christian Helmuth
parent 30c6feb86e
commit df27cc87b5
11 changed files with 233 additions and 5 deletions

View File

@ -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_ */

View File

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

View File

@ -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();

View File

@ -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 <typename T, typename... ARGS>
void _call_new(ARGS &&... args)

View File

@ -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 <typename T>
static void apply_with_boot_info(T const &fn)
{
fn(_boot_info());
}
};
#endif /* _CORE__PLATFORM_H_ */

View File

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

View File

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

View File

@ -95,6 +95,9 @@ void Thread::exception(Cpu & cpu)
}
void Thread::_call_suspend() { }
void Thread::_call_cache_coherent_region() { }

View File

@ -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 <kernel/thread.h>
#include <kernel/pd.h>
#include <hw/spec/x86_64/acpi.h>
#include <platform_pd.h>
#include <kernel/main.h>
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<Hw::Acpi_generic *>(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<Hw::Acpi_generic *>(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() { }

View File

@ -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 <cpu/cpu_state.h>
#include <pd_session_component.h>
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) { }

View File

@ -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"]]