UACPI

From OSDev Wiki
Jump to: navigation, search
ACPI
Fixed Tables
Differentiated Tables
Tools/Libs

ACPICA
uACPI
LAI

uACPI is a portable and easy-to-integrate implementation of the Advanced Configuration and Power Interface (ACPI).

Its main focuses are full NT compatibility, safety, and minimization of stack usage by avoiding recursion entirely.

Extra documentation can be found in the project's GitHub repo.

Contents

Features

  • A complete & well-tested AML interpreter
  • Operation region subsystem, support for user-defined address space handlers & builtin handlers for SystemMemory/SystemIO/PCI_Config/TableData
  • User-defined device notify handlers & dispatch
  • A complete resource subsystem. Every resource defined by ACPI 6.5 (last release) is supported
  • Sleep API, allowing reset, transition to any sleep state, wake vector programming API
  • An advanced event subsystem, supporting GPE/fixed events, wake, implicit notify, AML handlers
  • PCI routing table retrieval & interrupt model API
  • Device search API

uACPI has been tested and confirmed to work on many real computers, both new and old, desktops and laptops. It has also been extensively fuzzed against both real world AML blobs & its own test suite.

The test suite runs on every commit and tests multiple types of configuration, including 64 and 32 bit builds on Windows (MSVC), Linux (GCC) and MacOS (AppleClang).

Why use uACPI if ACPICA is a thing?

This is discussed in great detail in the project's README.

Integrating into a kernel

Refer to the project's README for detailed instructions on how to do it.

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(uacpi_phys_addr rsdp_phys_addr) {
    /*
     * Set up the initialization parameters structure that configures uACPI
     * behavior both at bring up and during runtime.
     */
    uacpi_init_params init_params = {
        /*
         * Physical address of the RSDP structure that we have either found
         * ourselves manually by scanning memory or (ideally) provided to us by
         * the bootloader.
         */
        .rsdp = rsdp_phys_addr,
 
        /*
         * Set up the runtime parameters, such as log level or behavior flags.
         */
        .rt_params = {
            /*
             * Set the log level to TRACE, this is a bit verbose but perhaps
             * okay for now since we're just getting started. We can change this
             * to INFO later on, which is the recommended level for release
             * builds. There's also the loudest UACPI_LOG_DEBUG log level, which
             * is recommended to pin down lockups or hangs.
             */
            .log_level = UACPI_LOG_TRACE,
 
            /*
             * Don't set any behavior flags, the defaults should work on most
             * hardware.
             */
            .flags = 0,
        },
    };
 
    /*
     * Proceed to the first step of the initialization. This loads all tables,
     * brings the event subsystem online, and enters ACPI mode.
     */
    uacpi_status ret = uacpi_initialize(&init_params);
    if (uacpi_unlikely_error(ret)) {
        log_error("uacpi_initialize error: %s", uacpi_status_to_string(ret));
        return -ENODEV;
    }
 
    /*
     * Load the AML namespace. This feeds DSDT and all SSDTs to the interpreter
     * for execution.
     */
    ret = uacpi_namespace_load();
    if (uacpi_unlikely_error(ret)) {
        log_error("uacpi_namespace_load error: %s", uacpi_status_to_string(ret));
        return -ENODEV;
    }
 
    /*
     * Initialize the namespace. This calls all necessary _STA/_INI AML methods,
     * as well as _REG for registered operation region handlers.
     */
    ret = uacpi_namespace_initialize();
    if (uacpi_unlikely_error(ret)) {
        log_error("uacpi_namespace_initialize error: %s", uacpi_status_to_string(ret));
        return -ENODEV;
    }
 
    /*
     * Tell uACPI that we have marked all GPEs we wanted for wake (even though we haven't
     * actually marked any, as we have no power management support right now). This is
     * needed to let uACPI enable all unmarked GPEs that have a corresponding AML handler.
     * These handlers are used by the firmware to dynamically execute AML code at runtime
     * to e.g. react to thermal events or device hotplug.
     */
    ret = uacpi_finalize_gpe_initialization();
    if (uacpi_unlikely_error(ret)) {
        log_error("uACPI GPE initialization error: %s", uacpi_status_to_string(ret));
        return -ENODEV;
    }
 
    /*
     * That's it, uACPI is now fully initialized and working! You can proceed to
     * using any public API at your discretion. The next recommended step is namespace
     * enumeration and device discovery so you can bind drivers to ACPI objects.
     */
    return 0;
}

Code examples

Shutting Down the System

#include <uacpi/sleep.h>
 
int system_shutdown(void) {
    /*
     * Prepare the system for shutdown.
     * This will run the \_PTS & \_SST methods, if they exist, as well as
     * some work to fetch the \_S5 and \_S0 values to make system wake
     * possible later on.
     */
    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;
    }
 
    /*
     * This is where we disable interrupts to prevent anything from
     * racing with our shutdown sequence below.
     */
    disable_interrupts();
 
    /*
     * Actually do the work of entering the sleep state by writing to the hardware
     * registers with the values we fetched during preparation.
     * This will also disable runtime events and enable only those that are
     * needed for wake.
     */
    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;
    }
 
    /*
     * Technically unreachable code, but leave it here to prevent the compiler
     * from complaining.
     */
    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>
 
/*
 * This handler will be called by uACPI from an interrupt context,
 * whenever a power button press is detected.
 */
static uacpi_interrupt_ret handle_power_button(uacpi_handle ctx) {
    /*
     * Shut down right here using the helper we have defined above.
     *
     * Note that it's generally terrible practice to run any AML from
     * an interrupt handler, as it's allowed to allocate, map, sleep,
     * stall, acquire mutexes, etc. So, if possible in your kernel,
     * instead schedule the shutdown callback to be run in a normal
     * preemptible context later.
     */
    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 of the more 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), attach an address space handler
  • Find a power button object in the namespace, attach a notify handler
  • Detect the general purpose event number used by the embedded controller (_GPE method), 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 notifications with value 0x80 (S0 Power Button Pressed)

Refer to 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.

Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox