Categories: Linux

I’ve heard about ACPI (Advanced Configuration and Power Interface) many times, but didn’t really understand what it was: seemed to be some magical firmware thing related to device management. I’ve finally spent some time reading up on it, and it isn’t so complicated.

Every computer system has some stuff on the motherboard: USB controllers, fans, display backlights, etc. All this should be accessable from the operating system. However x86 systems have only two ways that a program can interact with stuff: via read/write of memory-space addresses (ie any CPU instruction that uses an address), or read/write of io-space addresses (ie the IN/OUT instructions). So all these external devices are listening on the memory or io bus (which might be the same physical thing), waiting for write commands or read requests to “their” address. But what address? That often isn’t standardized anywhere, ie there needs to be some system-specific configuration somewhere that an operating-system can query to find which devices exist and which addresses they can be contacted on.

Some devices can be “discovered”, the most common being those on the PCI bus; given a known PCI bus an OS can find out which devices are on it and map them to free IO-space or memory-space address ranges. But the PCI bus controller itself cannot be discovered; the OS needs to somehow just know which addresses it responds to. The PCI bus controller is usually mapped to a specific pair of IO addresses. However there are many devices that are not standardised. And developing such standards is a slow and tedious process. Note for example that as part of the PCIe specification, a new way of accessing PCI configuration space was defined that uses only (physical) memory-space addresses (no io-space), and that the exact memory-space address is manufacturer-specific - but will be available in the ACPI tables.

It is of course possible to just encode this information directly into device drivers; a USB controller driver might have a table of addresses for different models of PC and choose the right addresses based on querying the host hardware type. That’s not very elegant or reliable though. Or a driver can “try all possibilities” - but such probing can potentially cause unexpected side-effects.

The BIOS can potentially provide a function to return the necessary data. But (a) that again requires consensus from multiple manufacturers and (b) calling the BIOS isn’t something OSes like to do for various reasons.

Device trees are (roughly) a json-style datastructure describing the hardware and related io/mem addresses for a system. The device-tree info is then stored in some suitable location in the firmware. The OS then just needs to know one address: where to find the device-tree definition. This solution is used extensively in linux for embedded systems.

ACPI is a more flexible, but much more complex, solution to the same problem. As with device trees, firmware holds a table of information which is loaded at startup. Well, in the case of ACPI there are half-a-dozen tables, but the end result is a tree of information describing the hardware. ACPI also defines a virtual machine language or bytecode (AML) with instructions like “read memory” or “write io”; ACPI table entries can then hold simple programs which specify a sequence of reads/writes to io or memory addresses (and possibly a few other things). The operating system kernel then must contain an interpreter for this bytecode.

Device drivers in the OS can use the ACPI library to perform tasks like finding all built-in devices of a specific type by looking in the ACPI tables (actually, calling an ACPI function to return that info). And they can perform tasks like “disable builtin wireless” by invoking the appropriate ACPI method; the AML interpreter loads and executes the corresponding instructions from the ACPI tables, which results in calls to the “OS services layer API” to read/write the appropriate IO or memory addresses. Result: the OS can access devices by knowing the right ACPI “names” without needing to know the exact IO or memory addresses that the hardware manufacturer mapped those devices to.

Interrupts are a third kind of interaction, and the ways in which builtin devices are connected to the system can vary between systems. I presume that the ACPI firmware for a system will also contain information that allows an OS to interpret interrupts correctly for the local system, but haven’t looked into that specifically.

The ACPICA project provides an open-source implementation of much of the ACPI specification. The ACPICA code is meant to run with kernel privileges, but is os-independent in that it does not use any windows/bsd/linux/etc APIs directly; instead it defines an “OS Services Layer” API through which ACPICA performs operations such as memory-allocation and all memory and IO reads/writes. Whichever operating system is on the host provides its own implementation of the OS services layer. ACPICA is logically divided into the following parts:

  • an AML interpreter;
  • table management: functions that an OS can call to load the contents of “ACPI tables”
  • namespace management: functions for finding/iterating-over lists of available tables, lists of available devices, etc
  • resource management: functions for installing interrupt-handlers, etc
  • hardware management: functions for enabling/disabling ACPI as a whole, modifying “registers”, changing the sleep-state of devices or CPUs, etc
  • event handling: functions for configuring callbacks for various device events (similar to interrupt-handlers??)

AML code actually manipulates devices by making calls back into the OS services layer to read/write IO-space or memory addresses, and to install traditional interrupt-handlers. There is therefore no magic integration between ACPI and the hardware: it is a repository of “system knowledge” in the form of AML instructions and hard-coded IO/memory addresses suitable for specific hardware, but all actual interaction with devices is always done via the operating system. The ACPI module within an OS is really an isolated bubble somewhat like a user-space process (though more performance-sensitive!).

The data in the primary ACPI tables is loaded at ACPI init time, forming a tree of “scope” objects. A scope has a “name” (and therefore a path) and may contain “methods” (sequences of AML instructions). There are about 10 standard “scopes” (aka namespaces), grouping together information about topics such as “event stuff”, “thermal stuff”, “processor stuff”. The most important ACPI table is called the DSDT. To see all tables: ls /sys/firmware/acpi/tables.