hw/x86: add ACPI tables for resume

The ACPI table FACS and FADT are required to support ACPI suspend/resume. The
commits add the lookup of the ACPI table in bootstrap and the general usage
of the ACPI registers via the MMIO framework.

Issue #4669
This commit is contained in:
Alexander Boettcher 2022-11-03 15:31:01 +01:00 committed by Christian Helmuth
parent dc1996d289
commit 32b1aa605a
4 changed files with 320 additions and 37 deletions

View File

@ -45,7 +45,7 @@ class Bootstrap::Platform
{
Memory_region_array early_ram_regions { };
Memory_region_array late_ram_regions { };
Mmio_space const core_mmio;
Mmio_space core_mmio;
unsigned cpus { ::Board::NR_OF_CPUS };
::Board::Boot_info info { };

View File

@ -170,43 +170,37 @@ Bootstrap::Platform::Board::Board()
uint64_t const table_addr = acpi_rsdp.xsdt ? acpi_rsdp.xsdt : acpi_rsdp.rsdt;
if (table_addr) {
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_addr);
auto rsdt_xsdt_lambda = [&](auto paddr_table) {
addr_t const table_virt_addr = paddr_table;
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_virt_addr);
if (!memcmp(table->signature, "FACP", 4)) {
info.acpi_fadt = addr_t(table);
auto mem_aligned = paddr_table & _align_mask(12);
auto mem_size = align_addr(paddr_table + table->size, 12) - mem_aligned;
core_mmio.add({ mem_aligned, mem_size });
}
if (memcmp(table->signature, "APIC", 4))
return;
Hw::for_each_apic_struct(*table,[&](Hw::Apic_madt const *e){
if (e->type == Hw::Apic_madt::LAPIC) {
Hw::Apic_madt::Lapic lapic(e);
/* check if APIC is enabled in hardware */
if (lapic.valid())
cpus ++;
}
});
};
auto table = reinterpret_cast<Hw::Acpi_generic *>(table_addr);
if (!memcmp(table->signature, "RSDT", 4)) {
Hw::for_each_rsdt_entry(*table, [&](uint32_t paddr_table) {
addr_t const table_virt_addr = paddr_table;
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_virt_addr);
if (memcmp(table->signature, "APIC", 4))
return;
Hw::for_each_apic_struct(*table,[&](Hw::Apic_madt const *e){
if (e->type == Hw::Apic_madt::LAPIC) {
Hw::Apic_madt::Lapic lapic(e);
/* check if APIC is enabled in hardware */
if (lapic.valid())
cpus ++;
}
});
});
Hw::for_each_rsdt_entry(*table, rsdt_xsdt_lambda);
} else if (!memcmp(table->signature, "XSDT", 4)) {
Hw::for_each_xsdt_entry(*table, [&](uint64_t paddr_table) {
addr_t const table_virt_addr = paddr_table;
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_virt_addr);
if (memcmp(table->signature, "APIC", 4))
return;
Hw::for_each_apic_struct(*table,[&](Hw::Apic_madt const *e){
if (e->type == Hw::Apic_madt::LAPIC) {
Hw::Apic_madt::Lapic lapic(e);
/* check if APIC is enabled in hardware */
if (lapic.valid())
cpus ++;
}
});
});
Hw::for_each_xsdt_entry(*table, rsdt_xsdt_lambda);
}
}
}

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2018-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.
@ -20,6 +20,8 @@ namespace Hw {
struct Acpi_generic;
struct Apic_madt;
struct Acpi_fadt;
struct Acpi_facs;
template <typename FUNC>
void for_each_rsdt_entry(Hw::Acpi_generic &, FUNC);
@ -78,6 +80,292 @@ struct Hw::Apic_madt
} __attribute__((packed));
/* ACPI spec 5.2.9 and ACPI GAS 5.2.3.2 */
struct Hw::Acpi_fadt : Genode::Mmio
{
enum Addressspace { IO = 0x1 };
enum { FW_OFFSET_EXT = 0x84, SLEEP_CONTROL_REG = 236 };
struct Size : Register < 0x04, 32> { };
struct Fw_ctrl : Register < 0x24, 32> { };
struct Fw_ctrl_ext : Register <FW_OFFSET_EXT, 64> { };
struct Pm1a_cnt_blk : Register < 64, 32> {
struct Slp_typ : Bitfield < 10, 3> { };
struct Slp_ena : Bitfield < 13, 1> { };
};
struct Pm1b_cnt_blk : Register < 68, 32> {
struct Slp_typ : Bitfield < 10, 3> { };
struct Slp_ena : Bitfield < 13, 1> { };
};
struct Gpe0_blk : Register < 80, 32> { };
struct Gpe1_blk : Register < 84, 32> { };
struct Gpe0_blk_len : Register < 92, 8> { };
struct Gpe1_blk_len : Register < 93, 8> { };
struct Pm1_cnt_len : Register < 89, 8> { };
struct Pm1a_cnt_blk_ext : Register < 172, 32> {
struct Addressspace : Bitfield < 0, 8> { };
struct Width : Bitfield < 8, 8> { };
};
struct Pm1a_cnt_blk_ext_addr : Register < 172 + 4, 64> { };
struct Pm1b_cnt_blk_ext : Register < 184, 32> {
struct Addressspace : Bitfield < 0, 8> { };
struct Width : Bitfield < 8, 8> { };
};
struct Pm1b_cnt_blk_ext_addr : Register < 184 + 4, 64> { };
struct Gpe0_blk_ext : Register < 220, 32> {
struct Addressspace : Bitfield < 0, 8> { };
struct Width : Bitfield < 8, 8> { };
};
struct Gpe0_blk_ext_addr : Register < 220 + 4, 64> { };
struct Gpe1_blk_ext : Register < 232, 32> {
struct Addressspace : Bitfield < 0, 8> { };
struct Width : Bitfield < 8, 8> { };
};
struct Gpe1_blk_ext_addr : Register < 232 + 4, 64> { };
/**
* Read from I/O port
*/
Genode::uint8_t inb(Genode::uint16_t port)
{
Genode::uint8_t res;
asm volatile ("inb %w1, %0" : "=a"(res) : "Nd"(port));
return res;
}
Genode::uint16_t inw(Genode::uint16_t port)
{
Genode::uint16_t res;
asm volatile ("inw %w1, %0" : "=a"(res) : "Nd"(port));
return res;
}
Genode::uint32_t inl(Genode::uint16_t port)
{
Genode::uint32_t res;
asm volatile ("inl %w1, %0" : "=a"(res) : "Nd"(port));
return res;
}
/**
* Write to I/O port
*/
inline void outb(Genode::uint16_t port, Genode::uint8_t val)
{
asm volatile ("outb %0, %w1" : : "a"(val), "Nd"(port));
}
inline void outw(Genode::uint16_t port, Genode::uint16_t val)
{
asm volatile ("outw %0, %w1" : : "a"(val), "Nd"(port));
}
inline void outl(Genode::uint16_t port, Genode::uint32_t val)
{
asm volatile ("outl %0, %w1" : : "a"(val), "Nd"(port));
}
Acpi_fadt(Acpi_generic const * a) : Mmio(Genode::addr_t(a)) { }
addr_t facs() const
{
addr_t facs { };
if (read<Size>() >= FW_OFFSET_EXT + 8)
facs = read<Fw_ctrl_ext>();
if (!facs)
facs = read<Fw_ctrl>();
return facs;
}
void clear_gpe0_status()
{
auto constexpr half_register = true;
return _write<Gpe0_blk_len, Gpe0_blk, Gpe0_blk_ext::Width,
Gpe0_blk_ext::Addressspace, Gpe0_blk_ext_addr>(~0ULL,
half_register);
}
void clear_gpe1_status()
{
auto constexpr half_register = true;
return _write<Gpe1_blk_len, Gpe1_blk, Gpe1_blk_ext::Width,
Gpe1_blk_ext::Addressspace, Gpe1_blk_ext_addr>(~0ULL,
half_register);
}
unsigned read_cnt_blk()
{
auto const pm1_a = _read<Pm1_cnt_len, Pm1a_cnt_blk,
Pm1a_cnt_blk_ext::Width,
Pm1a_cnt_blk_ext::Addressspace,
Pm1a_cnt_blk_ext_addr>();
auto const pm1_b = _read<Pm1_cnt_len, Pm1b_cnt_blk,
Pm1b_cnt_blk_ext::Width,
Pm1b_cnt_blk_ext::Addressspace,
Pm1b_cnt_blk_ext_addr>();
return pm1_a | pm1_b;
}
void write_cnt_blk(unsigned value_a, unsigned value_b)
{
_write<Pm1_cnt_len, Pm1a_cnt_blk, Pm1a_cnt_blk_ext::Width,
Pm1a_cnt_blk_ext::Addressspace, Pm1a_cnt_blk_ext_addr>(value_a);
_write<Pm1_cnt_len, Pm1b_cnt_blk, Pm1b_cnt_blk_ext::Width,
Pm1b_cnt_blk_ext::Addressspace, Pm1b_cnt_blk_ext_addr>(value_b);
}
void suspend(Genode::uint8_t typ_slp_a, Genode::uint8_t typ_slp_b)
{
auto cnt = read_cnt_blk();
auto value_a = cnt, value_b = cnt;
Pm1a_cnt_blk::Slp_typ::set(value_a, typ_slp_a);
Pm1a_cnt_blk::Slp_ena::set(value_a, 1);
Pm1b_cnt_blk::Slp_typ::set(value_b, typ_slp_b);
Pm1b_cnt_blk::Slp_ena::set(value_b, 1);
write_cnt_blk(value_a, value_b);
}
template <typename REG_LEN, typename BLK_ADDR, typename BLK_WIDTH_EXT,
typename BLK_AS_EXT, typename BLK_ADDR_EXT, typename T>
unsigned _access(bool hw_read, T value, bool half_register = false,
bool half_offset = false)
{
unsigned long long raw_io_addr = 0ull;
unsigned width_bits = 0u;
auto const register_len = read<REG_LEN>();
auto const reg_size = read<Size>();
if (reg_size >= SLEEP_CONTROL_REG && read<BLK_ADDR_EXT>() != 0ull) {
if (register_len)
width_bits = register_len * 8;
else {
width_bits = read<BLK_WIDTH_EXT>();
}
auto const as = read<BLK_AS_EXT>();
if (as != Addressspace::IO) {
Genode::error("unsupported address space access method ", as);
return 0;
}
raw_io_addr = read<BLK_ADDR_EXT>();
} else if (read<BLK_ADDR>() != 0 && register_len != 0) {
width_bits = register_len * 8;
raw_io_addr = read<BLK_ADDR>();
}
if (!width_bits || !raw_io_addr)
return 0u;
/* I/O address has to be 16 bit */
if (raw_io_addr >= (1ull << 16) || width_bits >= (1ull << 16)) {
Genode::error("too large I/O address ", raw_io_addr, " ", width_bits);
return 0u;
}
Genode::uint16_t io_addr = Genode::uint16_t(raw_io_addr);
if (half_register)
width_bits /= 2;
if (half_offset)
io_addr += Genode::uint16_t(width_bits) / 2 / 8;
if (hw_read) {
switch (width_bits) {
case 8:
return inb(io_addr);
case 16:
return inw(io_addr);
case 32:
return inl(io_addr);
default:
Genode::error("unsupported width for I/O IN : ", width_bits);
}
} else {
switch (width_bits) {
case 8:
outb(io_addr, Genode::uint8_t(value));
break;
case 16:
outw(io_addr, Genode::uint16_t(value));
break;
case 32:
outl(io_addr, Genode::uint32_t(value));
break;
case 64:
outl(io_addr, Genode::uint32_t(value));
if (sizeof(value) == 8)
outl(io_addr + 4, Genode::uint32_t(value >> 32));
break;
default:
Genode::error("unsupported width for I/O OUT : ", width_bits);
}
}
return 0u;
}
template <typename REG_LEN, typename BLK_ADDR, typename BLK_WIDTH_EXT,
typename BLK_AS_EXT, typename BLK_ADDR_EXT>
unsigned _read(bool half_register = false, bool half_offset = false)
{
enum { READ = true };
unsigned value = 0;
return _access<REG_LEN, BLK_ADDR, BLK_WIDTH_EXT, BLK_AS_EXT,
BLK_ADDR_EXT>(READ, value, half_register, half_offset);
}
template <typename REG_LEN, typename BLK_ADDR, typename BLK_WIDTH_EXT,
typename BLK_AS_EXT, typename BLK_ADDR_EXT, typename T>
void _write(T value, bool half_register = false, bool half_offset = false)
{
enum { WRITE = false };
_access<REG_LEN, BLK_ADDR, BLK_WIDTH_EXT, BLK_AS_EXT,
BLK_ADDR_EXT>(WRITE, value, half_register, half_offset);
}
};
struct Hw::Acpi_facs : Genode::Mmio
{
struct Length : Register < 0x04, 32> { };
struct Fw_wake_vector : Register < 0x0c, 32> { };
struct Fw_wake_vector_ext : Register < 0x18, 64> { };
void wakeup_vector(addr_t const entry)
{
auto len = read<Length>();
if (len >= 0x0c + 4)
write<Fw_wake_vector>(unsigned(entry));
if (len >= 0x18 + 8)
write<Fw_wake_vector_ext>(0);
}
Acpi_facs(addr_t const mmio) : Mmio(mmio) { }
};
template <typename FUNC>
void Hw::for_each_rsdt_entry(Hw::Acpi_generic &rsdt, FUNC fn)
{

View File

@ -39,6 +39,7 @@ struct Hw::Pc_board::Boot_info
Acpi_rsdp acpi_rsdp { };
Framebuffer framebuffer { };
Genode::addr_t efi_system_table { 0 };
Genode::addr_t acpi_fadt { 0 };
Boot_info() {}
Boot_info(Acpi_rsdp const &acpi_rsdp,