Code Examples
Initialization
Below is an example of basic uACPI initialization sequence that enters ACPI mode, parses tables, brings the event system online, and finally loads & initializes the namespace.
#include <uacpi/uacpi.h>
#include <uacpi/event.h>
int acpi_init(void) {
uacpi_status ret = uacpi_initialize(0);
if (uacpi_unlikely_error(ret)) {
log_error("uacpi_initialize error: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
ret = uacpi_namespace_load();
if (uacpi_unlikely_error(ret)) {
log_error("uacpi_namespace_load error: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
ret = uacpi_namespace_initialize();
if (uacpi_unlikely_error(ret)) {
log_error("uacpi_namespace_initialize error: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
ret = uacpi_finalize_gpe_initialization();
if (uacpi_unlikely_error(ret)) {
log_error("uACPI GPE initialization error: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
return 0;
}
Code Examples
Namespace Enumeration & Finding Devices
There are multiple ways to implement device discovery for an ACPI namespace. Below we discuss the most common approaches and their pros and cons.
Let Devices Discover Themselves
In this approach, we don't use a centralized bus system, but instead write an ad-hoc find/discover() function for each supported device, then register it somewhere so that it's called by kernel code during initialization.
#include <uacpi/utilities.h>
#include <uacpi/resources.h>
#define PS2K_PNP_ID "PNP0303"
static uacpi_iteration_decision match_ps2k(void *user, uacpi_namespace_node *node)
{
uacpi_resources *kb_res;
uacpi_status ret = uacpi_get_current_resources(node, &kb_res);
if (uacpi_unlikely_error(ret)) {
log_error("unable to retrieve PS2K resources: %s", uacpi_status_to_string(ret));
return UACPI_ITERATION_DECISION_NEXT_PEER;
}
ps2k_create_device(...);
uacpi_free_resources(kb_res);
return UACPI_ITERATION_DECISION_CONTINUE;
}
void find_ps2_keyboard()
{
uacpi_find_devices(PS2K_PNP_ID, match_ps2k, NULL);
}
void find_acpi_devices(void) {
find_ps2_keyboard();
find_ps2_mouse();
find_i2c();
find_power_button();
}
This is a very simple approach, but it has several drawbacks:
- Very slow: the entire namespace must be enumerated every time
- Binary bloat: more devices means more ad-hoc find methods
- Error-prone: more code duplication, more space for errors
Treat ACPI Namespace as a Bus
In this approach, we treat the ACPI namespace as a bus in our kernel and let devices provide a way to identify themselves.
#include <uacpi/uacpi.h>
#include <uacpi/namespace.h>
#include <uacpi/utilities.h>
#include <uacpi/resources.h>
struct acpi_driver {
const char *device_name;
const char *const *pnp_ids;
int (*device_probe)(uacpi_namespace_node *node, uacpi_namespace_node_info *info);
struct acpi_driver *next;
};
void acpi_register_driver(struct acpi_driver *driver);
#include <acpi_bus.h>
#define PS2K_PNP_ID "PNP0303"
static const char *const ps2k_pnp_ids[] = {
PS2K_PNP_ID,
NULL,
};
static int ps2k_probe(uacpi_namespace_node *node, uacpi_namespace_node_info *info)
{
uacpi_resources *kb_res;
uacpi_status st = uacpi_get_current_resources(node, &kb_res);
if (uacpi_unlikely_error(st)) {
log_error("unable to retrieve PS2K resources: %s", uacpi_status_to_string(st));
return -ENODEV;
}
int ret = ps2k_create_device(...);
uacpi_free_resources(kb_res);
return ret;
}
static acpi_driver ps2k_driver = {
.device_name = "PS2 Keyboard",
.pnp_ids = ps2k_pnp_ids,
.device_probe = ps2k_probe,
};
int ps2k_init(void)
{
acpi_register_driver(&ps2k_driver);
return 0;
}
#include <acpi_bus.h>
static struct acpi_driver *acpi_drivers_head;
void acpi_register_driver(struct acpi_driver *driver)
{
struct acpi_driver *next = acpi_drivers_head;
acpi_drivers_head = driver;
driver->next = next;
}
static uacpi_iteration_decision acpi_init_one_device(
void *ctx, uacpi_namespace_node *node, uacpi_u32 node_depth
)
{
uacpi_namespace_node_info *info;
(void)node_depth;
uacpi_status ret = uacpi_get_namespace_node_info(node, &info);
if (uacpi_unlikely_error(ret)) {
const char *path = uacpi_namespace_node_generate_absolute_path(node);
log_error("unable to retrieve node %s information: %s",
path, uacpi_status_to_string(ret));
uacpi_free_absolute_path(path);
return UACPI_ITERATION_DECISION_CONTINUE;
}
struct acpi_driver *drv = NULL;
if (info->flags & UACPI_NS_NODE_INFO_HAS_HID) {
}
if (drv == NULL && (info->flags & UACPI_NS_NODE_INFO_HAS_CID)) {
}
if (drv != NULL) {
drv->device_probe(node, info);
}
uacpi_free_namespace_node_info(info);
return UACPI_ITERATION_DECISION_CONTINUE;
}
void acpi_bus_enumerate()
{
uacpi_namespace_for_each_child(
uacpi_namespace_root(), acpi_init_one_device, UACPI_NULL,
UACPI_OBJECT_DEVICE_BIT, UACPI_MAX_DEPTH_ANY, UACPI_NULL
);
}
This approach is more scalable, faster, and involves far less code duplication — though it does require more upfront design work to get going.
Shutting Down the System
#include <uacpi/sleep.h>
int system_shutdown(void) {
uacpi_status ret = uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S5);
if (uacpi_unlikely_error(ret)) {
log_error("failed to prepare for sleep: %s", uacpi_status_to_string(ret));
return -EIO;
}
disable_interrupts();
ret = uacpi_enter_sleep_state(UACPI_SLEEP_STATE_S5);
if (uacpi_unlikely_error(ret)) {
log_error("failed to enter sleep: %s", uacpi_status_to_string(ret));
return -EIO;
}
return 0;
}
Hooking Up the Power Button
The example below hooks up the power button press using a fixed event callback.
#include <uacpi/event.h>
static uacpi_interrupt_ret handle_power_button(uacpi_handle ctx) {
system_shutdown();
return UACPI_INTERRUPT_HANDLED;
}
int power_button_init(void) {
uacpi_status ret = uacpi_install_fixed_event_handler(
UACPI_FIXED_EVENT_POWER_BUTTON,
handle_power_button, UACPI_NULL
);
if (uacpi_unlikely_error(ret)) {
log_error("failed to install power button event callback: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
return 0;
}
Note that some modern hardware routes the power button in a more complicated way, via an embedded controller. In order to hook that up you will need to:
- Write an embedded controller driver
- Find the EC device in the AML namespace (PNP ID
PNP0C09 or ECDT table) and attach an address space handler
- Find a power button object in the namespace and attach a notify handler
- Detect the general purpose event number used by the embedded controller (
_GPE method) and install a handler for it
- In the event handler, execute the
QR_EC command to find out the index of the query requested by the EC
- Run the requested query by executing the corresponding
EC._QXX control method in AML
- If this query was for a power button press, you will receive a notification with value
0x80 (S0 Power Button Pressed)
Refer to the managarm kernel EC driver to see an example of how this may be done.
More Examples
You can look at the managarm kernel source to see more examples of how you might use uACPI to implement various kernel features.