The Pedigree Project 0.1
AcpiPower.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "Acpi.h"
3
4#if ACPI
5#include "pedigree/kernel/Log.h"
6#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
7#include "pedigree/kernel/processor/VirtualAddressSpace.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include "AcpiS5.h"
11
12namespace {
13uint16_t readControl(uint16_t port) {
14 uint16_t value;
15 asm volatile("inw %1, %0" : "=a"(value) : "Nd"(port));
16 return value;
17}
18
19void writeControl(uint16_t port, uint16_t value) {
20 asm volatile("outw %0, %1" : : "a"(value), "Nd"(port));
21}
22
23void writeCommand(uint16_t port, uint8_t value) {
24 asm volatile("outb %0, %1" : : "a"(value), "Nd"(port));
25}
26} // namespace
27
28void Acpi::initialisePowerManagement() {
29 const uint8_t* fadt = reinterpret_cast<const uint8_t*>(m_pFacp);
30 if (m_pFacp->header.length >= 129 && (m_pFacp->flags & (1U << 10))) {
31 uint64_t address = 0;
32 MemoryCopy(&address, fadt + 120, sizeof(address));
33 // FADT RESET_REG is a Generic Address Structure. The fixed register
34 // permits an eight-bit write; other address spaces need their own accessors.
35 if (fadt[116] == 1 && fadt[117] == 8 && fadt[118] == 0 && fadt[119] <= 1 && address &&
36 address <= 0xffff) {
37 m_ResetPort = address;
38 m_ResetValue = fadt[128];
39 NOTICE("ACPI: reset port " << Hex << m_ResetPort);
40 }
41 }
42
43 // This path implements conventional PM1 I/O registers, not HW_REDUCED_ACPI.
44 if ((m_pFacp->flags & (1U << 20)) || m_pFacp->pm1ControlLength < 2 ||
45 !m_pFacp->pm1aControlBlock || m_pFacp->pm1aControlBlock > 0xfffe ||
46 m_pFacp->pm1bControlBlock > 0xfffe)
47 return;
48
49 uint64_t dsdtAddress = m_pFacp->dsdt;
50 if (m_pFacp->header.length >= 148) {
51 uint64_t extendedAddress;
52 MemoryCopy(&extendedAddress, fadt + 140, sizeof(extendedAddress));
53 if (extendedAddress)
54 dsdtAddress = extendedAddress;
55 }
56 if (!dsdtAddress || dsdtAddress > ~uintptr_t(0))
57 return;
58
59 const size_t pageSize = PhysicalMemoryManager::getPageSize();
60 const physical_uintptr_t page = dsdtAddress & ~(pageSize - 1);
61 const size_t offset = dsdtAddress - page;
64 MemoryRegion dsdt("ACPI DSDT power data");
66 if (!pmm.allocateRegion(dsdt,
67 (offset + sizeof(SystemDescriptionTableHeader) + pageSize - 1) / pageSize,
69 return;
70 const auto* table = dsdt.convertPhysicalPointer<SystemDescriptionTableHeader>(dsdtAddress);
71 const size_t length = table->length;
72 if (table->signature != 0x54445344 || length < sizeof(*table) || length > 1024 * 1024)
73 return;
74 dsdt.free();
75 if (!pmm.allocateRegion(dsdt, (offset + length + pageSize - 1) / pageSize, flags,
77 return;
78 table = dsdt.convertPhysicalPointer<SystemDescriptionTableHeader>(dsdtAddress);
79 if (table->length != length || !checksum(table))
80 return;
81 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(table);
82 if (AcpiS5::mayHaveSleepHooks(bytes + sizeof(*table), length - sizeof(*table))) {
83 WARNING("ACPI: firmware sleep methods require an interpreter; static poweroff unavailable");
84 return;
85 }
86
87 const size_t entries = (m_pRsdt->length - sizeof(*m_pRsdt)) / sizeof(uint32_t);
88 const uint8_t* entry = reinterpret_cast<const uint8_t*>(m_pRsdt) + sizeof(*m_pRsdt);
89 for (size_t i = 0; i < entries; ++i) {
90 uint32_t address;
91 MemoryCopy(&address, entry + i * sizeof(address), sizeof(address));
92 const uint64_t base = m_AcpiMemoryRegion.physicalAddress();
93 const uint64_t regionSize = m_AcpiMemoryRegion.size();
94 if (address < base || address - base > regionSize ||
95 regionSize - (address - base) < sizeof(SystemDescriptionTableHeader))
96 return;
97 const auto* secondary =
98 m_AcpiMemoryRegion.convertPhysicalPointer<SystemDescriptionTableHeader>(address);
99 if (secondary->signature != 0x54445353 && secondary->signature != 0x54445350)
100 continue;
101 if (secondary->length < sizeof(*secondary) ||
102 secondary->length > regionSize - (address - base) || !checksum(secondary))
103 return;
104 const auto* aml = reinterpret_cast<const uint8_t*>(secondary) + sizeof(*secondary);
105 if (AcpiS5::mayHaveSleepHooks(aml, secondary->length - sizeof(*secondary))) {
106 WARNING(
107 "ACPI: secondary firmware sleep methods require an interpreter; static poweroff "
108 "unavailable");
109 return;
110 }
111 }
112 m_PowerOffValid =
113 AcpiS5::find(bytes + sizeof(*table), bytes + length, m_SleepTypeA, m_SleepTypeB);
114 if (m_PowerOffValid) {
115 NOTICE("ACPI: static S5 sleep types " << Dec << m_SleepTypeA << ", " << m_SleepTypeB);
116 } else {
117 WARNING("ACPI: no supported static root _S5 package; poweroff unavailable");
118 }
119}
120
121void Acpi::setPowerManagement(const PowerManagement* provider) {
122 __atomic_store_n(&m_PowerManagement, provider, __ATOMIC_RELEASE);
123}
124
125bool Acpi::prepareShutdown(bool powerOff) {
126 const auto* provider = __atomic_load_n(&m_PowerManagement, __ATOMIC_ACQUIRE);
127 return !provider || provider->prepare(powerOff);
128}
129
130void Acpi::reset() {
131 const auto* provider = __atomic_load_n(&m_PowerManagement, __ATOMIC_ACQUIRE);
132 if (provider && provider->reset) {
133 provider->reset();
134 }
135 if (!m_ResetPort)
136 return;
137 writeCommand(m_ResetPort, m_ResetValue);
138 for (size_t i = 0; i < 100000; ++i)
139 asm volatile("pause");
140}
141
142bool Acpi::supportsPowerOff() const {
143 const auto* provider = __atomic_load_n(&m_PowerManagement, __ATOMIC_ACQUIRE);
144 if (provider && provider->powerOff) {
145 return true;
146 }
147 return m_PowerOffValid && ((readControl(m_pFacp->pm1aControlBlock) & 1) ||
148 (m_pFacp->smiCommandPort && m_pFacp->smiCommandPort <= 0xffff &&
149 m_pFacp->acpiEnableCommand));
150}
151
152const char* Acpi::powerOff() {
153 const auto* provider = __atomic_load_n(&m_PowerManagement, __ATOMIC_ACQUIRE);
154 if (provider && provider->powerOff) {
155 return provider->powerOff();
156 }
157 if (!m_PowerOffValid) {
158 return "ACPI: no supported S5 power-off method is available.";
159 }
160 const uint16_t portA = m_pFacp->pm1aControlBlock;
161 const uint16_t portB = m_pFacp->pm1bControlBlock;
162 if (!(readControl(portA) & 1)) {
163 if (!m_pFacp->smiCommandPort || m_pFacp->smiCommandPort > 0xffff ||
164 !m_pFacp->acpiEnableCommand) {
165 return "ACPI: firmware provides no usable ACPI-mode enable command.";
166 }
167 // SCI interrupts remain disabled during this terminal transition. Do not
168 // enable ACPI mode during normal operation without an SCI handler.
169 writeCommand(m_pFacp->smiCommandPort, m_pFacp->acpiEnableCommand);
170 size_t attempts = 1000000;
171 while (!(readControl(portA) & 1) && --attempts)
172 asm volatile("pause");
173 if (!attempts) {
174 ERROR_NOLOCK("ACPI: timed out enabling ACPI mode for power off");
175 return "ACPI: timed out enabling ACPI mode for power-off.";
176 }
177 }
178
179 const uint16_t sleepMask = (7U << 10) | (1U << 13);
180 const uint16_t controlA = (readControl(portA) & ~sleepMask) | (m_SleepTypeA << 10);
181 const uint16_t controlB = portB ? (readControl(portB) & ~sleepMask) | (m_SleepTypeB << 10) : 0;
182 // Program both sleep types before either SLP_EN write starts the transition.
183 writeControl(portA, controlA);
184 if (portB)
185 writeControl(portB, controlB);
186 asm volatile("wbinvd" : : : "memory");
187 writeControl(portA, controlA | (1U << 13));
188 if (portB)
189 writeControl(portB, controlB | (1U << 13));
190 for (size_t i = 0; i < 1000000; ++i)
191 asm volatile("pause");
192 return "ACPI: static S5 register writes returned without powering off.";
193}
194#endif
Special memory entity in the kernel's virtual address space.
static PhysicalMemoryManager & instance()
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124