HardwareEnableWithDSDT

Summary

This page is going to tell how to read DSDT/SSDT table and how it affects your system.

*CAUTION* Writing an experimental ACPI driver and running 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 contain information about platform and system definitions 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 SSDT.

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.

  1. Register a driver on \_SB.HKDV by using its HID HKEY1122
  2. When driver initial, execute the method INIT and register a input device.
  3. When driver receive a notify with event number 0x09, report KEY_MUTE to the input device.

Screenshot.png
Well, a sample of this ACPI driver is list below.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/err.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
#include <asm/uaccess.h>
#include <linux/input.h>

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", &params, 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);

Additional information

For some of design like Thinkpads and Ideapads. The query bit are the same for each hotkey. It means in notify function, we need to query the source of notify and tell which key is pressed, then report the keycode.

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.

KernelTeam/HardwareEnableWithDSDT (last edited 2016-08-05 18:38:47 by dannf)