The Pedigree Project 0.1
AcpiEc.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "AcpiEc.h"
3
4#if X86 || X64
5#include "pedigree/kernel/LockGuard.h"
6#include "pedigree/kernel/Log.h"
7#include "pedigree/kernel/process/Mutex.h"
8#include "pedigree/kernel/processor/Processor.h"
9#include "pedigree/kernel/time/Time.h"
10
11#include <uacpi/namespace.h>
12#include <uacpi/opregion.h>
13#include <uacpi/resources.h>
14#include <uacpi/uacpi.h>
15#include <uacpi/utilities.h>
16
17namespace {
18constexpr size_t MaxControllers = 4;
19constexpr uint8_t OutputFull = 1;
20constexpr uint8_t InputFull = 2;
21constexpr uint8_t ReadCommand = 0x80;
22constexpr uint8_t WriteCommand = 0x81;
23constexpr auto EcTimeout = 100 * Time::Multiplier::Millisecond;
24constexpr const char* EcIds[] = {"PNP0C09", nullptr};
25
26struct Ec {
27 uacpi_namespace_node* node = nullptr;
28 uint16_t dataPort = 0;
29 uint16_t commandPort = 0;
30 Mutex mutex;
31};
32
33Ec controllers[MaxControllers];
34size_t controllerCount = 0;
35bool installed = false;
36
37uint8_t readPort(uint16_t port) {
38 uint8_t value;
39 asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
40 return value;
41}
42
43void writePort(uint16_t port, uint8_t value) {
44 asm volatile("outb %0, %1" : : "a"(value), "Nd"(port));
45}
46
47bool waitStatus(const Ec& ec, uint8_t mask, uint8_t expected) {
48 const auto deadline = Time::getTicks() + EcTimeout;
49 do {
50 if ((readPort(ec.commandPort) & mask) == expected) {
51 return true;
52 }
54 } while (Time::getTicks() < deadline);
55 return (readPort(ec.commandPort) & mask) == expected;
56}
57
58bool readByte(const Ec& ec, uint8_t offset, uint8_t& value) {
59 if (!waitStatus(ec, InputFull | OutputFull, 0)) {
60 return false;
61 }
62 writePort(ec.commandPort, ReadCommand);
63 if (!waitStatus(ec, InputFull, 0)) {
64 return false;
65 }
66 writePort(ec.dataPort, offset);
67 if (!waitStatus(ec, OutputFull, OutputFull)) {
68 return false;
69 }
70 value = readPort(ec.dataPort);
71 return true;
72}
73
74bool writeByte(const Ec& ec, uint8_t offset, uint8_t value) {
75 if (!waitStatus(ec, InputFull | OutputFull, 0)) {
76 return false;
77 }
78 writePort(ec.commandPort, WriteCommand);
79 if (!waitStatus(ec, InputFull, 0)) {
80 return false;
81 }
82 writePort(ec.dataPort, offset);
83 if (!waitStatus(ec, InputFull, 0)) {
84 return false;
85 }
86 writePort(ec.dataPort, value);
87 return waitStatus(ec, InputFull, 0);
88}
89
90uacpi_status regionOperation(uacpi_region_op operation, uacpi_handle opaque) {
91 if (operation == UACPI_REGION_OP_ATTACH) {
92 const auto* data = static_cast<uacpi_region_attach_data*>(opaque);
93 return data->generic_info.base < 256 &&
94 data->generic_info.length <= 256 - data->generic_info.base
95 ? UACPI_STATUS_OK
96 : UACPI_STATUS_INVALID_ARGUMENT;
97 }
98 if (operation == UACPI_REGION_OP_DETACH) {
99 return UACPI_STATUS_OK;
100 }
101 if (operation != UACPI_REGION_OP_READ && operation != UACPI_REGION_OP_WRITE) {
102 return UACPI_STATUS_INVALID_ARGUMENT;
103 }
104
105 auto* data = static_cast<uacpi_region_rw_data*>(opaque);
106 auto* ec = static_cast<Ec*>(data->handler_context);
107 if (!ec || !data->byte_width || data->byte_width > 8 || data->offset > 255 ||
108 data->byte_width > 256 - data->offset) {
109 return UACPI_STATUS_INVALID_ARGUMENT;
110 }
111
112 LockGuard<Mutex> guard(ec->mutex);
113 if (operation == UACPI_REGION_OP_READ) {
114 data->value = 0;
115 }
116 for (uint8_t i = 0; i < data->byte_width; ++i) {
117 const uint8_t address = static_cast<uint8_t>(data->offset + i);
118 uint8_t value = 0;
119 const bool ok = operation == UACPI_REGION_OP_READ
120 ? readByte(*ec, address, value)
121 : writeByte(*ec, address, static_cast<uint8_t>(data->value >> (i * 8)));
122 if (!ok) {
123 ERROR("ACPI EC: timed out at offset " << Hex << static_cast<unsigned>(address));
124 return UACPI_STATUS_HARDWARE_TIMEOUT;
125 }
126 if (operation == UACPI_REGION_OP_READ) {
127 data->value |= static_cast<uint64_t>(value) << (i * 8);
128 }
129 }
130 return UACPI_STATUS_OK;
131}
132
133uacpi_iteration_decision findController(void*, uacpi_namespace_node* node, uacpi_u32) {
134 if (controllerCount < MaxControllers && uacpi_device_matches_pnp_id(node, EcIds)) {
135 controllers[controllerCount++].node = node;
136 }
137 return UACPI_ITERATION_DECISION_CONTINUE;
138}
139
140uacpi_iteration_decision findPort(void* opaque, uacpi_resource* resource) {
141 auto* ec = static_cast<Ec*>(opaque);
142 uint16_t port = 0;
143 if (resource->type == UACPI_RESOURCE_TYPE_IO) {
144 if (resource->io.minimum != resource->io.maximum || resource->io.length != 1) {
145 return UACPI_ITERATION_DECISION_CONTINUE;
146 }
147 port = resource->io.minimum;
148 } else if (resource->type == UACPI_RESOURCE_TYPE_FIXED_IO) {
149 if (resource->fixed_io.length != 1) {
150 return UACPI_ITERATION_DECISION_CONTINUE;
151 }
152 port = resource->fixed_io.address;
153 }
154 if (port) {
155 if (!ec->dataPort) {
156 ec->dataPort = port;
157 } else if (!ec->commandPort) {
158 ec->commandPort = port;
159 }
160 }
161 return UACPI_ITERATION_DECISION_CONTINUE;
162}
163} // namespace
164
165void installAcpiEcHandlersForShutdown() {
166 if (installed) {
167 return;
168 }
169 installed = true;
170 const auto status =
171 uacpi_namespace_for_each_child(uacpi_namespace_root(), findController, nullptr,
172 UACPI_OBJECT_DEVICE_BIT, UACPI_MAX_DEPTH_ANY, nullptr);
173 if (status != UACPI_STATUS_OK) {
174 WARNING("ACPI EC: device discovery failed: " << uacpi_status_to_string(status));
175 return;
176 }
177 if (!controllerCount) {
178 WARNING("ACPI EC: no PNP0C09 controller found");
179 }
180
181 for (size_t i = 0; i < controllerCount; ++i) {
182 Ec& ec = controllers[i];
183 uacpi_resources* resources = nullptr;
184 const auto resourceStatus = uacpi_get_current_resources(ec.node, &resources);
185 if (resourceStatus != UACPI_STATUS_OK || !resources) {
186 if (resources) {
187 uacpi_free_resources(resources);
188 }
189 WARNING("ACPI EC: current resources unavailable: " << uacpi_status_to_string(resourceStatus));
190 continue;
191 }
192 const auto parseStatus = uacpi_for_each_resource(resources, findPort, &ec);
193 uacpi_free_resources(resources);
194 if (parseStatus != UACPI_STATUS_OK || !ec.dataPort || !ec.commandPort ||
195 ec.dataPort == ec.commandPort) {
196 WARNING("ACPI EC: invalid data and command ports");
197 continue;
198 }
199
200 uint64_t globalLock = 0;
201 const auto lockStatus = uacpi_eval_simple_integer(ec.node, "_GLK", &globalLock);
202 if ((lockStatus != UACPI_STATUS_OK && lockStatus != UACPI_STATUS_NOT_FOUND) || globalLock) {
203 WARNING("ACPI EC: shared controller needs global-lock support");
204 continue;
205 }
206
207 // Firmware may place an EC operation region outside the EC device's
208 // namespace subtree. The first controller is the system-wide default;
209 // local handlers below override it for any additional controllers.
210 const auto installStatus = uacpi_install_address_space_handler(
211 i ? ec.node : uacpi_namespace_root(), UACPI_ADDRESS_SPACE_EMBEDDED_CONTROLLER,
212 regionOperation, &ec);
213 if (installStatus == UACPI_STATUS_OK) {
214 NOTICE("ACPI EC: shutdown handler on data port " << Hex << ec.dataPort << ", command port "
215 << ec.commandPort);
216 } else {
217 WARNING("ACPI EC: handler installation failed: " << uacpi_status_to_string(installStatus));
218 }
219 }
220}
221#else
222void installAcpiEcHandlersForShutdown() {}
223#endif
Definition Mutex.h:56
static void pause()
@ Hex
Definition Log.h:124