Summary
This page is going to tell how to read DSDT/SSDT table and how it affects your system.
*CAUTION* Write a experiment ACPI driver and run it may harm your computer or the data stored inside.
Overview of ACPI
ACPI is an interface between OS and BIOS.
When booting, BIOS will put
several tables that contains informations about platform and system
defination in the memory. OS reads those tables and knows what to do
when an event happen.
We care some tables most - They are DSDT and SSDTs.
DSDT is Differentiated System
Description Table which contains the Differentiated Definition Block
that supplies the implementation and configuration information about the
base system.
SSDT is Secondary System Description Table. SSDTs are a continuation of the DSDT.
Dump DSDT/SSDT
There are many ways to dump ACPI tables. The most easy way is to select and read out tables file from /sys/firmware/acpi/tables
user@here:~$ tree /sys/firmware/acpi/tables/
/sys/firmware/acpi/tables/
|-- APIC
|-- ATKG
|-- BOOT
|-- DBGP
|-- DSDT
|-- ECDT
|-- FACP
|-- FACS
|-- HPET
|-- MCFG
|-- OEMB
|-- SLIC
|-- SSDT
`-- dynamic
|-- SSDT2
|-- SSDT3
|-- SSDT4
`-- SSDT5
1 directory, 17 files
You can copy files out with root privilege and use iasl to decompile it
user@here:~$ sudo cp /sys/firmware/acpi/tables/DSDT ~/DSDT.bin
user@here:~$ sudo chown user:user /home/user/DSDT.bin
user@here:~$ iasl -d ~/DSDT.bin
Intel ACPI Component Architecture
AML Disassembler version 20090521 [Jun 30 2009]
Copyright (C) 2000 - 2009 Intel Corporation
Supports ACPI Specification Revision 3.0a
Loading Acpi table from file /home/user/DSDT.bin
Acpi table [DSDT] successfully installed and loaded
Pass 1 parse of [DSDT]
Pass 2 parse of [DSDT]
Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions)
............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Parsing completed
Disassembly completed, written to "/home/user/DSDT.dsl"
Now, you can find your DSDT at /home/user/DSDT.dsl
Information from /sys
There are
lots of ACPI devices described in DSDT/SSDT. DSDT/SSDT describes them in
a tree form. The following is a simple example for the ACPI device
tree.
user@here:~$ tree /sys/devices/LNXSYSTM\:00/
/sys/devices/LNXSYSTM:00/
|-- LNXCPU:00
|-- LNXCPU:01
|-- LNXPWRBN:00
|-- LNXTHERM:00
| `-- LNXTHERM:01
`-- device:00
|-- HKEY1122:00
|-- PNP0A08:00
| |-- ACPI0003:00
| |-- PNP0C02:00
| |-- PNP0C02:05
| |-- PNP0C0A:00
| |-- device:01
| | |-- ONE0013:00
| | |-- PNP0C04:00
| | `-- PNP0C09:00
| | `-- ONE0014:00
| |-- device:02
| | |-- device:03
| | | |-- device:04
| | | `-- device:05
| | |-- device:06
| | | |-- device:07
| | | `-- device:08
| | |-- device:09
| | |-- device:0a
| | |-- device:0b
| | `-- device:0c
| |-- device:0d
| |-- device:2f
| | `-- device:30
| `-- device:31
| |-- device:32
| `-- device:33
|-- PNP0C01:00
|-- PNP0C0D:00
|-- PNP0C0E:00
|-- PNP0C0F:00
|-- PNP0C0F:01
|-- PNP0C0F:02
|-- PNP0C0F:03
|-- PNP0C0F:04
|-- PNP0C0F:05
|-- PNP0C0F:06
`-- PNP0C0F:07
The
folder name starts with "device" means that this device has no HID
(Hardware ID). It's hard to bind a driver on it and execute its method,
so we will ignore them.
The folder name starts with
"PNP" or "ACPI" means that this device is generic ACPI device, not a
machine specific device. When we are going to enable some special
function, its not necessary to take a look.
The folder name starts with
"LNX" mean some root devices, they do not need an HID we know exact what
they are. In the above example, LNXSYSTM means \_SB (System Base),
LNXPWRBN is the power butten device, LNXCPU is the CPU and LNXTHERM is
the thermal zone defined in DSDT.
In the above example, we will look at HKEY1122, ONE0013 and ONE0014 to find out if there is anything we can enable.
Read DSDT/SSDTs
When you try to read DSDT, try to focus on a single device and its method. For example:
Scope (\_SB.PCI0.LPC.EC0)
{
Device (VCR0)
Name (_HID, "VCR010C")
{
Method (EPPC, 1, Serialized)
Method (DBLG, 0, NotSerialized)
}
}
And then you can read the content of each method and try to guess how it works. For example:
Method (EPPC, 1, Serialized)
{
Return (One)
}
It looks like a perfect method to identify this device - When we call this method, we shall have 1 in return.
Method (DBLG, 0, NotSerialized)
{
Store (0x0301, Local0)
Return (Local0)
}
In
the above example, Method DBLG pick up a value from 0x0301 (address
space defined in another table), and store temporarily and then return
it.
We can try to understand each method by reading its content or execute it experimentally and try to guess what it mean.
ACPI Event
When an
ACPI-handled event happened - for example: an ACPI-handled hotkey is
pressed, hardware will generate an interrupt of ACPI. You can monitor
that and find out there are relation between event and numbers of ACPI
Interrupt.
user@here:~$ cat /proc/interrupts | grep acpi
9: 32288 32006 IO-APIC-fasteoi acpi
The interrupt will be dispatch to a GPE event, you can find which GPE it is by monitoring /sys/firmware/acpi/interrupts/*
user@here:~$ cat /sys/firmware/acpi/interrupts/gpe1B
71718 enabled
Usually it belong to EC. You can check /proc/acpi/embedded_controller/EC?/info
user@here:~$ cat /proc/acpi/embedded_controller/EC0/info
gpe: 0x1b
ports: 0x66, 0x62
use global lock: no
You can monitor the EC GPE by adding a debug printk.
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index d6471bb..a76625b 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -528,6 +528,7 @@ static int acpi_ec_sync_query(struct acpi_ec *ec)
struct acpi_ec_query_handler *handler, *copy;
if ((status = acpi_ec_query_unlocked(ec, &value)))
return status;
+ pr_info(">>>>>> [EC GPE] Query bit = %02X\n", value);
list_for_each_entry(handler, &ec->list, node) {
if (value == handler->query_bit) {
/* have custom handler for this bit */
We
assume when we press mute key, the query bit is 0x1A. When this event
happen, EC driver will execute its method _Q1A. Then we can seek _Q1A
method in the DSDT/SSDT. The scope of this method shall be EC.
Scope (_SB.PCI0.SBRG.EC0)
{
......
Method (_Q1A, 0, NotSerialized)
{
If (HKON)
{
Notify (HKDV, 0x09)
}
}
......
}
It
tells us that when HKON is true, there will be a notify on HKDV and its
event number is 0x09. So, we shall know how to let HKON become true by
searching HKON in DSDT/SSDT.
Scope (_SB)
{
Name (HKON, Zero)
}
It tells us HKON is a variable at _SB and its default value is zero.
Scope (_SB)
{
Device (HKDV)
{
Name (_HID, "HKEY1122")
Method (INIT, 1, NotSerialized)
{
Store (One, HKON)
Return (Zero)
}
}
}
It
tells us there is a method \_SB.HKDV.INIT that helps us to let HKON
become true, and when HKON is true, there will be an notify with event
number 0x09 on \_SB.HKDV
Here is the plan of the driver.
- Register a driver on \_SB.HKDV by using its HID HKEY1122
- When driver initial, execute the method INIT and register a input device.
- When driver receive a notify with event number 0x09, report KEY_MUTE to the input device.
Well, a sample of this ACPI driver is list below.
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_DESCRIPTION("MyHotkey");
MODULE_LICENSE("GPL");
static struct input_dev *my_inputdev;
static void my_hotk_notify(struct acpi_device *device, u32 event)
{
if (event != 0x9)
return; /* This is not our key, do nothing */
input_report_key(my_inputdev, KEY_MUTE, 1); /* report key pressed */
input_sync(my_inputdev);
input_report_key(my_inputdev, KEY_MUTE, 0); /* report key released */
input_sync(my_inputdev);
}
static int my_hotk_add(struct acpi_device *device)
{
union acpi_object in_obj;
struct acpi_object_list params;
/* Run INIT method */
params.count = 1;
params.pointer = &in_obj;
in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = 0;
acpi_evaluate_object(device->handle, "INIT", ¶ms, NULL);
/* register input device */
my_inputdev = input_allocate_device();
my_inputdev->name = "MyHotKey";
my_inputdev->phys = "MyHotKey/input0";
my_inputdev->id.bustype = BUS_HOST;
set_bit(EV_KEY, my_inputdev->evbit);
set_bit(KEY_MUTE, my_inputdev->keybit);
input_register_device(my_inputdev);
return 0;
}
static int my_hotk_remove(struct acpi_device *device, int type)
{
return 0; /* This is an example, no error handling and exit functions */
}
static const struct acpi_device_id my_hotkey_ids[] = {
{"HKEY1122", 0},
{"", 0},
};
static struct acpi_driver my_hotk_driver = {
.name = "MyHotkey",
.class = "hotkey",
.owner = THIS_MODULE,
.ids = my_hotkey_ids,
.flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
.ops = {
.add = my_hotk_add,
.remove = my_hotk_remove,
.notify = my_hotk_notify,
},
};
static void __exit mylaptop_exit(void)
{
acpi_bus_unregister_driver(&my_hotk_driver);
}
static int __init mylaptop_init(void)
{
return acpi_bus_register_driver(&my_hotk_driver);
}
module_init(mylaptop_init);
module_exit(mylaptop_exit);
Some suggestions for ACPI driver
1. Try to build single driver for one ACPI device.
2. In driver add function,
use as many as possible ways to make sure the driver binds on the
correct device. Ex: DMI information, accessing empty method.