uACPI

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

CI

Features

Why would I use this over ACPICA?

1. Performance

uACPI shows a consistent speedup of about 3.5x over ACPICA in synthetic AML tests.

More details

Code that was tested:

Method (TOMS, 2, NotSerialized) {
    Return ((Arg1 - Arg0) / 10000)
}

Method (ADDM, 1, NotSerialized) {
    Local0 = 0

    While (Arg0) {
        Local0 += Arg0 + Arg0
        Arg0--
    }

    Return (Local0)
}

// Record start time
Local0 = Timer

// Run 10 million additions
Local1 = ADDM(10000000)

// Make sure the answer matches expected
If (Local1 != 0x5AF31112D680) {
    Printf("Bad test result %o", Local1)
    Return (1)
}

// Record end time
Local2 = Timer

Printf("10,000,000 additions took %o ms",
       ToDecimalString(TOMS(Local0, Local2)))

Compile options (acpiexec and uACPI's test-runner): -O3 -flto -march=znver4 -mtune=znver4
CPU: AMD Ryzen 9 9950X3D
Raw test scores (~average over 10 runs):

Raw difference: 3.5053x

Real hardware tests of the same operating system using uACPI vs ACPICA show at least a 1.75-2x speedup while measuring the time it takes to load the initial AML namespace.

More details

OS: proxima

Compile options: -O3 -flto

Test Subject 1

Specs: Gigabyte B550M S2H, AMD Ryzen 5800X, 64GB RAM
Firmware: F19d (10097 AML opcodes)

Results:

Raw difference: 2.0698x

Test Subject 2

Specs: Toshiba Portege R30-A, Intel Core i5-4200M, 4GB RAM
Firmware: 4.40 (4962 AML opcodes)

Results:

Raw difference: 1.7503x

2. NT-compatible from the ground up

Over the decades of development, ACPICA has accumulated a lot of workarounds for AML expecting NT-specific behaviors, and is still missing compatibility in a lot of critical aspects.

uACPI, on the other hand, is built to be natively NT-compatible without extra workarounds.

Some specific highlights include:

3. Fundamental safety

uACPI is built to always assume the worst about the AML byte code it's executing, and as such, has a more sophisticated object lifetime tracking system, as well as carefully designed handling for various edge-cases, including race conditions.

Some of the standard uACPI test cases crash both ACPICA, and the NT AML interpreters.

While a permanent fuzzing solution for uACPI is currently WIP, it has already been fuzzed quite extensively and all known issues have been fixed.

4. No recursion

Running at kernel level has a lot of very strict limitations, one of which is a tiny stack size, which can sometimes be only a few pages in length.

Of course, both ACPICA and uACPI have non-recursive AML interpreters, but there are still edge cases that cause potentially unbounded recursion.

One such example are the dynamic table load operators from AML (Load/LoadTable): these cause a linear growth in stack usage per call in ACPICA, whereas in uACPI these are treated as special method calls, and as such, don't increase stack usage whatsoever.

More detailed overview

Expressions within package:

Method (TEST) {
    Local0 = 10
    Local1 = Package { Local0 * 5 }
    Return (DerefOf(Local1[0]))
}

// ACPICA: AE_SUPPORT, Expressions within package elements are not supported
// Windows, uACPI: Local0 = 50
Local0 = TEST()

Packages outside of a control method:

// ACPICA: internal error
// Windows, uACPI: ok
Local0 = Package { 1 }

Reference rebind semantics:

Local0 = 123
Local1 = RefOf(Local0)

// ACPICA: Local1 = 321, Local0 = 123
// Windows, uACPI: Local1 = reference->Local0, Local0 = 321
Local1 = 321

Increment/Decrement:

Local0 = 123
Local1 = RefOf(Local0)

// ACPICA: error
// Windows, uACPI: Local0 = 124
Local1++

Multilevel references:

Local0 = 123
Local1 = RefOf(Local0)
Local2 = RefOf(Local1)

// ACPICA: Local3 = reference->Local0
// Windows, uACPI: Local3 = 123
Local3 = DerefOf(Local2)

Implict-cast semantics:

Name (TEST, "BAR")

// ACPICA: TEST = "00000000004F4F46"
// Windows, uACPI: TEST = "FOO"
TEST = 0x4F4F46

Buffer size mutability:

Name (TEST, "XXXX")
Name (VAL, "")

// ACPICA: TEST = "LONGSTRING"
// Windows, UACPI: TEST = "LONG"
TEST = "LONGSTRING"

// ACPICA: VAL = "FOO"
// Windows, UACPI: VAL = ""
VAL = "FOO"

Returning a reference to a local object:

Method (TEST) {
    Local0 = 123

    // Use-after-free in ACPICA, perfectly fine in uACPI
    Return (RefOf(Local0))
}

Method (FOO) {
    Name (TEST, 123)

    // Use-after-free in ACPICA, object lifetime prolonged in uACPI (node is still removed from the namespace)
    Return (RefOf(TEST))
}

CopyObject into self:

Method (TEST) {
    CopyObject(123, TEST)
    Return (1)
}

// Segfault in ACPICA, prints 1 in uACPI  
Debug = TEST()

// Unreachable in ACPICA, prints 123 in uACPI
Debug = TEST

There's even more examples, but this should be enough to demonstrate the fundamental differences in designs.

Integrating into a kernel

1. Add uACPI sources & include directories into your project

If you're using CMake

Simply add the following lines to your cmake:

include(uacpi/uacpi.cmake)

target_sources(
    my-kernel
    PRIVATE
    ${UACPI_SOURCES}
)

target_include_directories(
    my-kernel
    PRIVATE
    ${UACPI_INCLUDES}
)

If you're using Meson

Add the following lines to your meson.build:

uacpi = subproject('uacpi')

uacpi_sources = uacpi.get_variable('sources')
my_kernel_sources += uacpi_sources

uacpi_includes = uacpi.get_variable('includes')
my_kernel_includes += uacpi_includes

Any other build system

2. Implement/override platform-specific headers

uACPI defines all platform/architecture-specific functionality in a few headers inside include/uacpi/platform

All of the headers can be "implemented" by your project in a few ways:

Currently used platform-specific headers are:

3. Implement kernel API

uACPI relies on kernel-specific API to do things like mapping/unmapping memory, writing/reading to/from IO, PCI config space, and many more things.

This API is declared in kernel_api.h and is implemented by your kernel.

4. Initialize uACPI

That's it, uACPI is now integrated into your project.

You should proceed to initialization.
Refer to the uACPI page on osdev wiki to see a snippet for basic initialization, as well as some code examples of how you may want to use certain APIs.

All of the headers and APIs defined in uacpi are public and may be utilized by your project.
Anything inside uacpi/internal is considered private/undocumented and unstable API.

Developing and contributing

Most development work is fully doable in userland using the test runner.

Setting up an IDE:

Simply open tests/runner/CMakeLists.txt in your favorite IDE.

For Visual Studio:

cd tests\runner && mkdir build && cd build && cmake ..

Then just simply open the .sln file generated by cmake.

Running the test suite:

./tests/run_tests.py

If you want to contribute:

All contributions are very welcome!

Notable projects using uACPI & performance leaderboards

ProjectDescription(qemu w/ Q35 + KVM) ops/sCPU
proximaUnix-like microkernel-based operating system with uACPI running in userspace10,454,158AMD Ryzen 9 9950X3D
ilobilixYet another monolithic Linux clone wannabe. Currently under a rewrite8,703,286AMD Ryzen 9 9950X3D
Crescent2An NT driver compatible kernel and userspace6,818,418Intel Core i5-13600K
davixYet another unix-like by some bored nerd6,364,623Intel Core i7-13700K
IroncladFormally verified, hard real-time capable kernel written in SPARK and Ada6,196,937Intel Core i9-13900KS
ManagarmPragmatic microkernel-based OS with fully asynchronous I/O5,618,646Intel Core i7-14700K
ChronOSAnother basic hobby os held together by duct tape, made in rust5,416,703Intel Core Ultra 7 265KF
pmOSMicrokernel-based operating system written from scratch with uACPI running in userspace5,354,445AMD Ryzen 9 5900X
menixA minimal and expandable Unix-like operating system5,239,043Intel Core Ultra 7 265KF
OrangeUnix-like x86_64 OS with some microkernel features4,204,053AMD Ryzen 5 3600
AstralOperating system written in C which aims be POSIX-compliant4,189,189Intel Core i5-13600K
KeyronexLayered kernel with fundamentally asynchronous I/O and working set model-based memory management4,013,691AMD Ryzen 5800X
OBOSHybrid kernel with module loading4,003,820Intel Core Ultra 7 265F
bentobox64-bit SMP-enabled operating system targeting x86_64 and aarch643,709,263AMD Ryzen 7 5800X
NyauxKCMonolithic UNIX-like multi-architecture kernel1,966,580Intel Core i7-13700K
imaginarium64-bit os in zig supporting aarch64 and x86_641,908,315AMD Ryzen 7 3700X
ElysiumOSHybrid Unix-like kernel1,737,654AMD Ryzen 7 5800X3D
BadgerOSA monolithic lightweight UNIX clone1,018,518AMD Ryzen 5 3600
Retro RocketA BASIC powered 64-bit SMP multitasking OS441,329Intel Xeon E5-2680
HyraMonolithic UNIX-like OS by OSMORA.ORG199,873Intel Core i3-3220

License

MIT License

uACPI is licensed under the MIT License.
The full license text is provided in the LICENSE file inside the root directory.

Records

Functions

Enums

Aliases