Skip to content

ACPI for Developers

Gilligan94 edited this page Jul 27, 2013 · 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 analysing tons of tables, we accumulated enough knowledge to know how to use _DSM calls properly for quite every laptops, even Legacy Optimus ones.

If you're interested in more details, here is what we've seen (for Optimus laptops, Legacy one are a bit different) :

  • _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. By the way, calling _PS3 requires to call _DSM first with the following args : {0xF8,0xD8,0x86,0xA4,0xDA,0x0B,0x1B,0x47,0xA7,0x2B,0x60,0x42,0xA6,0xB5,0xBE,0xE0} 0x100 0x1A {0x1,0x0,0x0,0x3}.
  • _PS0 and _PS3 should be called using _DSM if needed with accurate args. However, Lekensteyn found a pci function that appears to be doing quite the same, i.e. that seems to disable the card with the same effectiveness. However, it can't wake up a card put in _PS3 via acpi_call.
  • Additional informations : they seem to be some special interfaces to use this function for Windows, often called WMMX in Asus laptops tables, and NVOP/NV3D in other ones. Their behaviors are hard to understand, and they're not easy to reverse, but they seems to build the proper args to call _DSM for Optimus needs. We're not sure that it won't be a waste of time to look further in this direction, and as we got everything working, we stopped our investigations here.

Important informations when playing with ACPI

  • You must never try to set a device to a state it is already in !!! Normally, if you're using proper calls, they're providing enough security to avoid problems. But if it's not the case (_ON or _OFF for example), Asking a card to go to D3 when already in could lead to crash, unability to use the card, ... -It is needed on the card before switching off because it can leads to some problems else. Before, that was also needed before going to sleep (the computer, not you :P), but thanks to Lekensteyn and his work in bbswitch, that isn't anymore needed.

Tools

A proof of concept for using ACPI easily has been developped by @mkottman, it's called acpi_call and the latest source is available in our acpi_call repo. Please note that even if some improvements were made by Lekensteyn, that's still a proof of concept and you can really break your computer if playing dangerously with it.

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 https://acpica.org/downloads, 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]

If you get the below error, edit the Makefile file and change LDFLAGS += -lpthread to LDFLAGS += -pthread.

osunixxf.o: In function 'AcpiOsCreateSemaphore':
osunixxf.c:(.text+0x308): undefined reference to 'sem_init'
osunixxf.o: In function 'AcpiOsDeleteSemaphore':
osunixxf.c:(.text+0x360): undefined reference to 'sem_destroy'
osunixxf.o: In function 'AcpiOsWaitSemaphore':
osunixxf.c:(.text+0x3c5): undefined reference to 'sem_trywait'
osunixxf.c:(.text+0x3df): undefined reference to 'sem_wait'
osunixxf.c:(.text+0x43b): undefined reference to 'sem_timedwait'
osunixxf.o: In function 'AcpiOsSignalSemaphore':
osunixxf.c:(.text+0x485): undefined reference to 'sem_post'
osunixxf.o: In function 'AcpiOsExecute':
osunixxf.c:(.text+0x7af): undefined reference to 'pthread_create'

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