Skip to content

ACPI for Developers

Lekensteyn edited this page Dec 11, 2011 · 19 revisions

please share your ACPI knowledge

ACPI is an abbreviation of Advanced Configuration and Power Interface. The standard can be found at http://acpi.info.

ACPI is defining states for the whole system and for each component independently.

System states are:

  • G0 (S0) : system fully ON.
  • G1 : sleeping. Several sub-levels are defined here:
  • S1 : power on suspend.
  • S2 : suspend to RAM
  • S3 : standby
  • S4 : hibernate
  • G2 (S5) : Soft Off. System still listening to some events (keyboard, USB, network) in order to start if received.
  • G3 (S6) : Mechanical OFF. Only CMOS is running.

Device states are:

  • D0 : ON.
  • D1, D2 : intermediate, depend on device. May not be present.
  • D3 : OFF.

When a device is in the D0 state, it could exist Performance states for it, starting from P0 (fully powered) and until P16 when accurate.

There are also CPU states, but they're not interesting for us.

The language used to manage ACPI is called ASL (ACPI Source Language). This language is, as far as we know, only described by the ACPI specs. However, as Intel gives out the code of the interpreter/compiler, we maybe be able to understand the language this way.

The language is compiled into AML (ACPI Machine Language) in a lot of tables, some of them are specific to the computer. List of tables :

  • RSDP (Root System Description Pointer)
  • RSDT (Root System Description Table)
  • DSDT (Differentiated System Description Table). This table contains the primary AML code for the system.
  • XSDT (Extended System Description Table)
  • FADT (Fixed ACPI Description Table, signature "FACP"). This table contains configuration information about the ACPI hardware and pointers to the FACS and DSDT tables.
  • FACS (Firmware ACPI Control Structure). This table contains the OS-to-firmware interface including the firmware waking vector and the Global Lock.
  • SBST (Smart Battery Table)
  • ECDT (Embedded Controller Boot Resources Table)
  • MADT (Multiple APIC Description Table)
  • SRAT (System Resource Affinity Table)
  • SLIT (System Locality Distance Information Table)
  • SLIC (Software Licensing Description Table)
  • SSDT (Secondary System Descriptor Tables). Contains additional AML code
  • THRM (CPU Thermal)

(Note (ArchangeGabriel) : I will add infos on these tables in the future, and also explain how to get them)

The AML code is stored in the BIOS, so a BIOS upgrade could change it (and often does).

In ASL, there are two different things :

  • Scopes : sort of path, root is _SB, childs are added like _SB.CHILD
  • Objects : they are contained in a path, and they are referred exactly like scopes, e.g. _SB.PATH.OBJT. They could be of five types:
  • Buffer : a group of n*8 8bit integer, e.g. {0x00, 0x01, 0x02, 0x03, 0xFC, 0xFD, 0xFE, 0xFF}
  • Integer : didn't determined size actually, will do that ASAP, they are something like 0xFFFFFFFFFFF.
  • Bin : 0x00, 0x01, but also found as Zero, One.
  • Function : a suit of instructions
  • String : never seen any as of today

All of those things are having a name of maximum four latin caracters or _ (e.g. FUNC, VAR, but also _VAR, ...).

Naming

I (Lekensteyn) could not find a good reference for the names used for the identifiers and was especially wondering what PEGP stands for. I've found a reference to PEG in the Intel documentation on the i7 processor, and from that, I've guessed the name below.

Extracting each part from \_SB_.PCI0.P0P2.PEGP._STA:

  • \_SB - System bus tree (Package)
  • PCI0 - PCI Bus (Bus Object)
  • P0P2 - Device (note that it's a P - Zero - P and not P - O - P.
  • PEGP - PCI Express Graphics Port (guessed)
  • _STA - a method for checking whether a device is enabled or not

Methods

  • _REG - initialize all Operation Regions
  • _STA, _INI - initialize Device, Processor and Thermal objects. If there is no _INI method, _STA won't be considered. _STA returns One if a device is enabled and Zero otherwise. If this method is not found on a device, it's assumed to be powered on.
  • _PRW - identify and define GPEs (General Purpose Event) that are used for wake events (suspend etc?)
  • _HID - get hardware ID, "PNP0A03" is PCI Root Bridge, "PNP0A08" for PCI Express)

Resources:

ACPI and the nvidia card

(Refer to Section 9.14.1: _DSM (Device Specific Method)) (Refer to Appendix A, Section 6: Display Device Class) (Refer to Appendix B: ACPI Extensions for Display Adapters)

What is interesting us here is theorically DSDT and SSDTs (there may be more than one SSDT table for a single computer), so we already started gathering those.

For now, we never see any card supporting D1 and D2. So, our goal here is to be able to switch between D0 and D3.

After analyzing some tables, there are some things to know :

  • _ON and _OFF funcs should never be called as standalone. Some computers are having a better behavior here by integrating them at the right place directly or by checking some values before running them. We will call those two functions level 1 ones.
  • What we need to call is _PS0 and _PS3. However, once again, these shouldn't be called as standalone. However, they look to have better stability/security mecanisms. We will call those functions level 2 ones. For example, we are actually sure that _PS3 ever requires to be run after a (correct) call to a certain _DSM function. This function would be the level 3 one.
  • What we figured out: _DSM is called by some others functions, actually we saw two sets of such functions: WMMX on some Asus models, NVOP/NV3D for every others. Let's call those functions level 4 ones.
  • Some functions require parameters, that is the case of DSM and method calling it. For DSM, args are generated by the function calling it, using its own args for that. But for those function, I'm guessing that we should use for argument some vars defined in the tables, as we can be pretty sure that they aren't here for nothing. But in order to do that, we need to reverse level 1 functions to understand what they do and what they need (actually, this seems useless, but to be sure, should be done), do the same for level 2 functions to understand what they need (and eventually what they do), reverse the level 3 function in order to understand what it does and what it expects as arguments in order to work, then reverse level 4 ones in order to understand what they do, how they generate args for level 3 one, and what are the expected args to do that.
  • You must never try to set a device to a state it is already in !!! Normally, level 4 calls, if triggered as they should be, should be verifying that. Asking a card to go to D3 when already in could lead to crash, unability to use the card, ...
  • We should power on the card before switching off or before going to sleep (for the computer, not you :P) because it can leads to some problems else.

Tools

ACPICA provides nice tools for working with this ACPI stuff. You may already have used iasl or acpixtract, but acpiexec is another useful tool. Download the source from http://www.acpica.org/downloads/unix_source_code.php, extract it and run:

cd acpica-unix-*/tools/acpiexec
make

This will create the acpiexec executable in the current directory. If you get an error like the below, edit the Makefile file and remove the -Werror flag from CWARNINGFLAGS.

../../os_specific/service_layers/osunixxf.c:352:29: error: variable 'Count' set but not used [-Werror=unused-but-set-variable]

acpiexec can be used for quickly getting all available methods, run acpiexec file.dat where file.dat is a DSDT or SSDT table generated by acpixtract. Do not pass the disassembled file from iasl, that won't work. After running something like acpiexec DSDT.dsl, you can view all methods by running the methods command. Run help in the acpiexec shell for more options.

A repository dedicated to tools for ACPI tables analysis can be found at https://github.com/Lekensteyn/acpi-stuff

PCI configuration space

While this has little to do with ACPI itself, this information is relevant for PM. If the card is disabled before suspend, lspci -d10de: -vvv will show something like this:

01:00.0 VGA compatible controller: nVidia Corporation GF108 [GeForce GT 425M] (rev ff) (prog-if ff)  
       !!! Unknown header type 7f

After resume, this is gone and the regular information is shown instead. With one big difference: the drivers won't load / X won't start. The nouveau will complain with:

[drm] nouveau 0000:01:00.0: Unsupported chipset 0xffffffff

This is caused by the fact that before suspend, the kernel saves the configuration space. If the card is disabled, the returned configuration space will contain enabled bits only (all 1's, hexadecimal FF...). On resume, the card is enabled (D0 state) and this configuration space is restored. Since the card is enabled, the configuration space is meaningful, but overwritten by junk data. After restoring the correct configuration space (which would be performed by rebooting as well), the driver would load again. This space can be written in /proc/bus/pci/01/00.0 assuming PCI Bus ID 01.00.0.

Consider the below side-by-side diff of the configuration space. On the left side, the card was on. The right side shows the state when resuming after the card was off.

0000000: de10 f00d  ....                0000000: de10 f00d  ....
0000004: 0601 1000  ....              | 0000004: 4705 1000  G...
0000008: a100 0003  ....                0000008: a100 0003  ....
000000c: 1000 0000  ....              | 000000c: ff00 0000  ....
0000010: 0000 00fb  ....              | 0000010: 0000 00ff  ....
0000014: 0c00 00f0  ....              | 0000014: 0c00 00f8  ....
0000018: 0000 0000  ....              | 0000018: ffff ffff  ....
000001c: 0c00 00f8  ....              | 000001c: 0c00 00fe  ....
0000020: 0000 0000  ....              | 0000020: ffff ffff  ....
0000024: 0120 0000  . ..              | 0000024: 81ff ffff  ....
0000028: 0000 0000  ....                0000028: 0000 0000  ....
000002c: 5815 3071  X.0q                000002c: 5815 3071  X.0q
0000030: 0000 0000  ....              | 0000030: 0100 f8ff  ....
0000034: 6000 0000  `...                0000034: 6000 0000  `...
0000038: 0000 0000  ....                0000038: 0000 0000  ....
000003c: 1001 0000  ....              | 000003c: ff01 0000  ....

(this diff was generated using diff -W 80 -y from data retrieved from /proc/bus/pci/01/00.0)

The configuration space is saved on suspend if no driver is loaded. See also http://www.mjmwired.net/kernel/Documentation/power/pci.txt#427 and http://www.mjmwired.net/kernel/Documentation/power/devices.txt#328

Clone this wiki locally