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
|
||||
{
|
||||
|
||||
class ACPI
|
||||
class ACPI : public Interruptable
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<void> initialize();
|
||||
|
@ -31,10 +31,16 @@ namespace Kernel::ACPI
|
|||
// This function will return only if there was an error
|
||||
void poweroff();
|
||||
|
||||
void handle_irq() override;
|
||||
|
||||
private:
|
||||
ACPI() = default;
|
||||
BAN::ErrorOr<void> initialize_impl();
|
||||
|
||||
FADT& fadt() { return *m_fadt; }
|
||||
|
||||
void acpi_event_task();
|
||||
|
||||
private:
|
||||
paddr_t m_header_table_paddr = 0;
|
||||
vaddr_t m_header_table_vaddr = 0;
|
||||
|
@ -49,6 +55,10 @@ namespace Kernel::ACPI
|
|||
};
|
||||
BAN::Vector<MappedPage> m_mapped_headers;
|
||||
|
||||
FADT* m_fadt { nullptr };
|
||||
|
||||
Semaphore m_acpi_event_semaphore;
|
||||
|
||||
bool m_hardware_reduced { false };
|
||||
BAN::RefPtr<AML::Namespace> m_namespace;
|
||||
};
|
||||
|
|
|
@ -144,6 +144,7 @@ namespace Kernel
|
|||
|
||||
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<void> mount(BAN::StringView source, BAN::StringView target);
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#include <kernel/ACPI/AML/Method.h>
|
||||
#include <kernel/ACPI/AML/Package.h>
|
||||
#include <kernel/BootInfo.h>
|
||||
#include <kernel/InterruptController.h>
|
||||
#include <kernel/IO.h>
|
||||
#include <kernel/Memory/PageTable.h>
|
||||
#include <kernel/Process.h>
|
||||
#include <kernel/Timer/Timer.h>
|
||||
|
||||
#define RSPD_SIZE 20
|
||||
|
@ -77,11 +79,11 @@ acpi_release_global_lock:
|
|||
|
||||
enum PM1Event : uint16_t
|
||||
{
|
||||
PM1_EVN_TMR_EN = 1 << 0,
|
||||
PM1_EVN_GBL_EN = 1 << 5,
|
||||
PM1_EVN_PWRBTN_EN = 1 << 8,
|
||||
PM1_EVN_SLPBTN_EN = 1 << 8,
|
||||
PM1_EVN_RTC_EN = 1 << 10,
|
||||
PM1_EVN_TMR = 1 << 0,
|
||||
PM1_EVN_GBL = 1 << 5,
|
||||
PM1_EVN_PWRBTN = 1 << 8,
|
||||
PM1_EVN_SLPBTN = 1 << 8,
|
||||
PM1_EVN_RTC = 1 << 10,
|
||||
PM1_EVN_PCIEXP_WAKE_DIS = 1 << 14,
|
||||
};
|
||||
|
||||
|
@ -315,10 +317,14 @@ acpi_release_global_lock:
|
|||
.vaddr = dsdt_vaddr
|
||||
}));
|
||||
|
||||
m_fadt = fadt;
|
||||
m_hardware_reduced = fadt->flags & (1 << 20);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_fadt == nullptr)
|
||||
Kernel::panic("No FADT found");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -404,21 +410,19 @@ acpi_release_global_lock:
|
|||
|
||||
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 |= (slp_typa.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
||||
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 |= (slp_typb.value() & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
||||
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);
|
||||
|
||||
// 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 (!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
|
||||
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
|
||||
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;
|
||||
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");
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
}
|
||||
|
||||
// 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->pm1b_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 | PM1_EVN_SLPBTN);
|
||||
}
|
||||
|
||||
dprintln("Entered ACPI mode");
|
||||
|
@ -488,7 +491,67 @@ acpi_release_global_lock:
|
|||
|
||||
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 {};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
BAN::ErrorOr<long> Process::sys_poweroff(int command)
|
||||
BAN::ErrorOr<long> Process::clean_poweroff(int command)
|
||||
{
|
||||
if (command != POWEROFF_REBOOT && command != POWEROFF_SHUTDOWN)
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
|
@ -1215,6 +1215,11 @@ namespace Kernel
|
|||
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)
|
||||
{
|
||||
LockGuard _(m_process_lock);
|
||||
|
|
Loading…
Reference in New Issue