Kernel: Add support for power button shutdown
This patch implements basic support for power button using ACPI fixed events. I still need to implement general purpose events and embedded controller for full power button support.
This commit is contained in:
parent
9fac5f94ba
commit
b89bafa165
|
@ -8,7 +8,7 @@
|
||||||
namespace Kernel::ACPI
|
namespace Kernel::ACPI
|
||||||
{
|
{
|
||||||
|
|
||||||
class ACPI
|
class ACPI : public Interruptable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static BAN::ErrorOr<void> initialize();
|
static BAN::ErrorOr<void> initialize();
|
||||||
|
@ -31,10 +31,16 @@ namespace Kernel::ACPI
|
||||||
// This function will return only if there was an error
|
// This function will return only if there was an error
|
||||||
void poweroff();
|
void poweroff();
|
||||||
|
|
||||||
|
void handle_irq() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ACPI() = default;
|
ACPI() = default;
|
||||||
BAN::ErrorOr<void> initialize_impl();
|
BAN::ErrorOr<void> initialize_impl();
|
||||||
|
|
||||||
|
FADT& fadt() { return *m_fadt; }
|
||||||
|
|
||||||
|
void acpi_event_task();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
paddr_t m_header_table_paddr = 0;
|
paddr_t m_header_table_paddr = 0;
|
||||||
vaddr_t m_header_table_vaddr = 0;
|
vaddr_t m_header_table_vaddr = 0;
|
||||||
|
@ -49,6 +55,10 @@ namespace Kernel::ACPI
|
||||||
};
|
};
|
||||||
BAN::Vector<MappedPage> m_mapped_headers;
|
BAN::Vector<MappedPage> m_mapped_headers;
|
||||||
|
|
||||||
|
FADT* m_fadt { nullptr };
|
||||||
|
|
||||||
|
Semaphore m_acpi_event_semaphore;
|
||||||
|
|
||||||
bool m_hardware_reduced { false };
|
bool m_hardware_reduced { false };
|
||||||
BAN::RefPtr<AML::Namespace> m_namespace;
|
BAN::RefPtr<AML::Namespace> m_namespace;
|
||||||
};
|
};
|
||||||
|
|
|
@ -144,6 +144,7 @@ namespace Kernel
|
||||||
|
|
||||||
BAN::ErrorOr<long> sys_sync(bool should_block);
|
BAN::ErrorOr<long> sys_sync(bool should_block);
|
||||||
|
|
||||||
|
static BAN::ErrorOr<long> clean_poweroff(int command);
|
||||||
BAN::ErrorOr<long> sys_poweroff(int command);
|
BAN::ErrorOr<long> sys_poweroff(int command);
|
||||||
|
|
||||||
BAN::ErrorOr<void> mount(BAN::StringView source, BAN::StringView target);
|
BAN::ErrorOr<void> mount(BAN::StringView source, BAN::StringView target);
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
#include <kernel/ACPI/AML/Method.h>
|
#include <kernel/ACPI/AML/Method.h>
|
||||||
#include <kernel/ACPI/AML/Package.h>
|
#include <kernel/ACPI/AML/Package.h>
|
||||||
#include <kernel/BootInfo.h>
|
#include <kernel/BootInfo.h>
|
||||||
|
#include <kernel/InterruptController.h>
|
||||||
#include <kernel/IO.h>
|
#include <kernel/IO.h>
|
||||||
#include <kernel/Memory/PageTable.h>
|
#include <kernel/Memory/PageTable.h>
|
||||||
|
#include <kernel/Process.h>
|
||||||
#include <kernel/Timer/Timer.h>
|
#include <kernel/Timer/Timer.h>
|
||||||
|
|
||||||
#define RSPD_SIZE 20
|
#define RSPD_SIZE 20
|
||||||
|
@ -77,11 +79,11 @@ acpi_release_global_lock:
|
||||||
|
|
||||||
enum PM1Event : uint16_t
|
enum PM1Event : uint16_t
|
||||||
{
|
{
|
||||||
PM1_EVN_TMR_EN = 1 << 0,
|
PM1_EVN_TMR = 1 << 0,
|
||||||
PM1_EVN_GBL_EN = 1 << 5,
|
PM1_EVN_GBL = 1 << 5,
|
||||||
PM1_EVN_PWRBTN_EN = 1 << 8,
|
PM1_EVN_PWRBTN = 1 << 8,
|
||||||
PM1_EVN_SLPBTN_EN = 1 << 8,
|
PM1_EVN_SLPBTN = 1 << 8,
|
||||||
PM1_EVN_RTC_EN = 1 << 10,
|
PM1_EVN_RTC = 1 << 10,
|
||||||
PM1_EVN_PCIEXP_WAKE_DIS = 1 << 14,
|
PM1_EVN_PCIEXP_WAKE_DIS = 1 << 14,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -315,10 +317,14 @@ acpi_release_global_lock:
|
||||||
.vaddr = dsdt_vaddr
|
.vaddr = dsdt_vaddr
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
m_fadt = fadt;
|
||||||
m_hardware_reduced = fadt->flags & (1 << 20);
|
m_hardware_reduced = fadt->flags & (1 << 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_fadt == nullptr)
|
||||||
|
Kernel::panic("No FADT found");
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,21 +410,19 @@ acpi_release_global_lock:
|
||||||
|
|
||||||
dprintln("Entering sleep state S5");
|
dprintln("Entering sleep state S5");
|
||||||
|
|
||||||
auto* fadt = static_cast<const FADT*>(get_header("FACP", 0));
|
uint16_t pm1a_data = IO::inw(fadt().pm1a_cnt_blk);
|
||||||
|
|
||||||
uint16_t pm1a_data = IO::inw(fadt->pm1a_cnt_blk);
|
|
||||||
pm1a_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
pm1a_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
||||||
pm1a_data |= (slp_typa.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
pm1a_data |= (slp_typa.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
||||||
pm1a_data |= PM1_CNT_SLP_EN;
|
pm1a_data |= PM1_CNT_SLP_EN;
|
||||||
IO::outw(fadt->pm1a_cnt_blk, pm1a_data);
|
IO::outw(fadt().pm1a_cnt_blk, pm1a_data);
|
||||||
|
|
||||||
if (fadt->pm1b_cnt_blk != 0)
|
if (fadt().pm1b_cnt_blk != 0)
|
||||||
{
|
{
|
||||||
uint16_t pm1b_data = IO::inw(fadt->pm1b_cnt_blk);
|
uint16_t pm1b_data = IO::inw(fadt().pm1b_cnt_blk);
|
||||||
pm1b_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
pm1b_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
||||||
pm1b_data |= (slp_typb.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
pm1b_data |= (slp_typb.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
||||||
pm1b_data |= PM1_CNT_SLP_EN;
|
pm1b_data |= PM1_CNT_SLP_EN;
|
||||||
IO::outw(fadt->pm1b_cnt_blk, pm1b_data);
|
IO::outw(fadt().pm1b_cnt_blk, pm1b_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,31 +434,30 @@ acpi_release_global_lock:
|
||||||
return BAN::Error::from_errno(EFAULT);
|
return BAN::Error::from_errno(EFAULT);
|
||||||
|
|
||||||
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/16_Waking_and_Sleeping/initialization.html#placing-the-system-in-acpi-mode
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/16_Waking_and_Sleeping/initialization.html#placing-the-system-in-acpi-mode
|
||||||
auto* fadt = static_cast<const FADT*>(get_header("FACP", 0));
|
|
||||||
|
|
||||||
// If not hardware-reduced ACPI and SCI_EN is not set
|
// If not hardware-reduced ACPI and SCI_EN is not set
|
||||||
if (!hardware_reduced() && !(IO::inw(fadt->pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
if (!hardware_reduced() && !(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
||||||
{
|
{
|
||||||
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#legacy-acpi-select-and-the-sci-interrupt
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#legacy-acpi-select-and-the-sci-interrupt
|
||||||
IO::outb(fadt->smi_cmd, fadt->acpi_enable);
|
IO::outb(fadt().smi_cmd, fadt().acpi_enable);
|
||||||
|
|
||||||
// Spec says to poll until SCI_EN is set, but doesn't specify timeout
|
// Spec says to poll until SCI_EN is set, but doesn't specify timeout
|
||||||
for (size_t i = 0; i < 100; i++)
|
for (size_t i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
if (IO::inw(fadt->pm1a_cnt_blk) & PM1_CNT_SCI_EN)
|
if (IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN)
|
||||||
break;
|
break;
|
||||||
SystemTimer::get().sleep(10);
|
SystemTimer::get().sleep(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(IO::inw(fadt->pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
if (!(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
||||||
{
|
{
|
||||||
dwarnln("Failed to enable ACPI mode");
|
dwarnln("Failed to enable ACPI mode");
|
||||||
return BAN::Error::from_errno(EINVAL);
|
return BAN::Error::from_errno(EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable power and sleep buttons
|
// Enable power and sleep buttons
|
||||||
IO::outw(fadt->pm1a_evt_blk + fadt->pm1_evt_len / 2, PM1_EVN_PWRBTN_EN | PM1_EVN_SLPBTN_EN);
|
IO::outw(fadt().pm1a_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
|
||||||
IO::outw(fadt->pm1b_evt_blk + fadt->pm1_evt_len / 2, PM1_EVN_PWRBTN_EN | PM1_EVN_SLPBTN_EN);
|
IO::outw(fadt().pm1b_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
|
||||||
}
|
}
|
||||||
|
|
||||||
dprintln("Entered ACPI mode");
|
dprintln("Entered ACPI mode");
|
||||||
|
@ -488,7 +491,67 @@ acpi_release_global_lock:
|
||||||
|
|
||||||
dprintln("Devices are initialized");
|
dprintln("Devices are initialized");
|
||||||
|
|
||||||
|
uint8_t irq = fadt().sci_int;
|
||||||
|
if (auto ret = InterruptController::get().reserve_irq(irq); ret.is_error())
|
||||||
|
dwarnln("Could not enable ACPI interrupt: {}", ret.error());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
set_irq(irq);
|
||||||
|
enable_interrupt();
|
||||||
|
Process::create_kernel([](void*) { get().acpi_event_task(); }, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ACPI::acpi_event_task()
|
||||||
|
{
|
||||||
|
auto get_fixed_event =
|
||||||
|
[&](uint16_t sts_port)
|
||||||
|
{
|
||||||
|
auto sts = IO::inw(sts_port);
|
||||||
|
auto en = IO::inw(sts_port + fadt().pm1_evt_len / 2);
|
||||||
|
if (auto pending = sts & en)
|
||||||
|
return pending & ~(pending - 1);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
uint16_t sts_port;
|
||||||
|
uint16_t pending;
|
||||||
|
|
||||||
|
sts_port = fadt().pm1a_evt_blk;
|
||||||
|
if (pending = get_fixed_event(sts_port); pending)
|
||||||
|
goto handle_event;
|
||||||
|
|
||||||
|
sts_port = fadt().pm1b_evt_blk;
|
||||||
|
if (pending = get_fixed_event(sts_port); pending)
|
||||||
|
goto handle_event;
|
||||||
|
|
||||||
|
// FIXME: this can cause missing of event if it happens between
|
||||||
|
// reading the status and blocking
|
||||||
|
m_acpi_event_semaphore.block_with_timeout(100);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handle_event:
|
||||||
|
if (pending & PM1_EVN_PWRBTN)
|
||||||
|
{
|
||||||
|
if (auto ret = Process::clean_poweroff(POWEROFF_SHUTDOWN); ret.is_error())
|
||||||
|
dwarnln("Failed to poweroff: {}", ret.error());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dwarnln("Unhandled ACPI fixed event {H}", pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
IO::outw(sts_port, pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACPI::handle_irq()
|
||||||
|
{
|
||||||
|
m_acpi_event_semaphore.unblock();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1198,7 +1198,7 @@ namespace Kernel
|
||||||
IDT::force_triple_fault();
|
IDT::force_triple_fault();
|
||||||
}
|
}
|
||||||
|
|
||||||
BAN::ErrorOr<long> Process::sys_poweroff(int command)
|
BAN::ErrorOr<long> Process::clean_poweroff(int command)
|
||||||
{
|
{
|
||||||
if (command != POWEROFF_REBOOT && command != POWEROFF_SHUTDOWN)
|
if (command != POWEROFF_REBOOT && command != POWEROFF_SHUTDOWN)
|
||||||
return BAN::Error::from_errno(EINVAL);
|
return BAN::Error::from_errno(EINVAL);
|
||||||
|
@ -1215,6 +1215,11 @@ namespace Kernel
|
||||||
return BAN::Error::from_errno(EUNKNOWN);
|
return BAN::Error::from_errno(EUNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<long> Process::sys_poweroff(int command)
|
||||||
|
{
|
||||||
|
return clean_poweroff(command);
|
||||||
|
}
|
||||||
|
|
||||||
BAN::ErrorOr<long> Process::sys_readdir(int fd, DirectoryEntryList* list, size_t list_size)
|
BAN::ErrorOr<long> Process::sys_readdir(int fd, DirectoryEntryList* list, size_t list_size)
|
||||||
{
|
{
|
||||||
LockGuard _(m_process_lock);
|
LockGuard _(m_process_lock);
|
||||||
|
|
Loading…
Reference in New Issue