UACPI: Difference between revisions

5,992 bytes added ,  29 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(21 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 ===
Line 174 ⟶ 363:
The example below hooks up the power button press using a fixed event callback.
 
<sourcesyntaxhighlight lang="c">
#include <uacpi/event.h>
 
Line 189 ⟶ 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 201 ⟶ 390:
);
if (uacpi_unlikely_error(ret)) {
log_error("failed to enterinstall sleeppower button event callback: %s", uacpi_status_to_string(ret));
return -ENODEV;
}
Line 207 ⟶ 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.
Line 215 ⟶ 404:
* 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
* Wait for notifications with value 0x80 (S0 Power Button Pressed).
* 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.