UACPI: Difference between revisions

6,259 bytes added ,  29 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
No edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(24 intermediate revisions by 2 users not shown)
Line 35:
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.
 
<sourcesyntaxhighlight lang="c">
#include <uacpi/uacpi.h>
#include <uacpi/event.h>
Line 53:
 
/*
* Set up the runtimelog level to parametersTRACE, suchthis asis loga levelbit orverbose behaviorbut flags.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.
*/
.rt_paramslog_level = {UACPI_LOG_TRACE,
/*
* 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,
},
};
 
Line 123 ⟶ 118:
return 0;
}
</syntaxhighlight>
</source>
 
== Code examples ==
=== Namespace Enumeration & Finding Devices ===
 
There are multiple ways to implement device discovery for an ACPI namespace, below we will discuss the most common ways and their pros and cons.
 
==== Let Devices Discover Themselves ====
 
In this approach, we don't use a centralized bus system, but instead write ad-hoc find/discover() function for each supported device, then register it somewhere
so that it's called by kernel code during initialization.
 
<syntaxhighlight lang="c">
// ps2k.c
#include <uacpi/utilities.h>
#include <uacpi/resources.h>
 
#define PS2K_PNP_ID "PNP0303"
 
static uacpi_ns_iteration_decision match_ps2k(void *user, uacpi_namespace_node *node)
{
// Found a PS2 keyboard! Do initialization below.
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_NS_ITERATION_DECISION_NEXT_PEER;
}
 
// Parse the resources to find the IRQ and IO ports the keyboard is connected to
// ...uacpi_for_each_resource()
 
ps2k_create_device(...);
 
uacpi_free_resources(kb_res);
 
return UACPI_NS_ITERATION_DECISION_CONTINUE;
}
 
void find_ps2_keyboard()
{
uacpi_find_devices(PS2K_PNP_ID, match_ps2k, NULL);
}
</syntaxhighlight>
 
<syntaxhighlight lang="c">
// acpi_init.c
void find_acpi_devices(void) {
find_ps2_keyboard();
find_ps2_mouse();
find_i2c();
find_power_button();
// ...and more
}
</syntaxhighlight>
 
As you can see it's a very simple approach, but it has lots of drawbacks:
* Very slow: we have to enumerate the entire namespace every time
* Binary bloat: more devices, 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 them.
 
<syntaxhighlight lang="c">
// acpi_bus.h
#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);
</syntaxhighlight>
 
<syntaxhighlight lang="c">
// ps2k.c
#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;
 
/* Parse the resources to find the IRQ and IO ports the keyboard is connected to
*
* Note that for a centralized system like that the resources could be passed
* to the device probe callback from common enumeration code at this point as
* well!
*/
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;
}
 
// Actually instantiate the device
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,
};
 
/*
* This is called either manually by the kernel, or put in the linker script
* in some known section, and executed by the kernel as part of driver initcalls.
* If it's a dynamically loadable module, then this is called on module load.
*/
int ps2k_init(void)
{
acpi_register_driver(&ps2k_driver);
return 0;
}
</syntaxhighlight>
 
<syntaxhighlight lang="c">
// acpi_bus.c
#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_ns_iteration_decision acpi_init_one_device(void *ctx, uacpi_namespace_node *node)
{
uacpi_namespace_node_info *info;
 
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_NS_ITERATION_DECISION_CONTINUE;
}
 
if (info->type != UACPI_OBJECT_DEVICE) {
// We probably don't care about anything but devices at this point
uacpi_free_namespace_node_info(info);
return UACPI_NS_ITERATION_DECISION_CONTINUE;
}
 
struct acpi_driver *drv = NULL;
 
if (info->flags & UACPI_NS_NODE_INFO_HAS_HID) {
// Match the HID against every existing acpi_driver pnp id list
}
 
if (drv == NULL && (info->flags & UACPI_NS_NODE_INFO_HAS_CID)) {
// Match the CID list against every existing acpi_driver pnp id list
}
 
if (drv != NULL) {
// Probe the driver and do something with the error code if desired
drv->device_probe(node, info);
}
 
uacpi_free_namespace_node_info(info);
return UACPI_NS_ITERATION_DECISION_CONTINUE;
}
 
void acpi_bus_enumerate()
{
uacpi_namespace_for_each_node_depth_first(
uacpi_namespace_root(), acpi_init_one_device, UACPI_NULL
);
}
</syntaxhighlight>
 
As you can see above, this approach is more scalable, faster, and involves way less code duplication.
It does require a lot mode code and design to get going initially though.
 
=== Shutting Down the System ===
<sourcesyntaxhighlight lang="c">
#include <uacpi/sleep.h>
 
Line 168 ⟶ 357:
return 0;
}
</syntaxhighlight>
</source>
 
=== Hooking Up the Power Button ===
 
The example below hooks up the power button press asusing a fixed event callback.
 
<syntaxhighlight lang="c">
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 EC driver, find the EC device in the AML namespace, attach an address
space handler to the EC node, then find a power button object in the namespace, attach a notify handler to the device and
then handle notifications with value 0x80 (S0 Power Button Pressed).
 
<source lang="c">
#include <uacpi/event.h>
 
Line 194 ⟶ 378:
* stall, acquire mutexes, etc. So, if possible in your kernel,
* instead schedule the shutdown callback to be run in a normal
* preemptablepreemptible context later.
*/
system_shutdown();
Line 206 ⟶ 390:
);
if (uacpi_unlikely_error(ret)) {
log_error("failed to enterinstall sleeppower button event callback: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
Line 212 ⟶ 396:
return 0;
}
</syntaxhighlight>
</source>
 
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 [https://uefi.org/specs/ACPI/6.5/12_Embedded_Controller_Interface_Specification.html 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 [https://github.com/managarm/managarm/blob/master/kernel/thor/system/acpi/ec.cpp managarm kernel EC driver] to see an example of how this may be done.
 
=== More Examples ===