SSDT 覆盖

为了支持 ACPI 开放式硬件配置(例如,开发板),我们需要一种方法来增强固件映像提供的 ACPI 配置。一个常见的例子是在开发板上的 I2C/SPI 总线上连接传感器。

虽然这可以通过创建内核平台驱动程序或使用更新的 ACPI 表重新编译固件映像来实现,但这两种方法都不实用:前者会扩散特定于板的内核代码,而后者需要访问通常不公开的固件工具。

由于 ACPI 支持 AML 代码中的外部引用,一种更实用的增强固件 ACPI 配置的方法是动态加载包含特定于板信息的由用户定义的 SSDT 表。

例如,要在 Minnowboard MAX 开发板的 I2C 总线上枚举通过 LSE 连接器 [1] 公开的 Bosch BMA222E 加速度计,可以使用以下 ASL 代码

DefinitionBlock ("minnowmax.aml", "SSDT", 1, "Vendor", "Accel", 0x00000003)
{
    External (\_SB.I2C6, DeviceObj)

    Scope (\_SB.I2C6)
    {
        Device (STAC)
        {
            Name (_HID, "BMA222E")
            Name (RBUF, ResourceTemplate ()
            {
                I2cSerialBus (0x0018, ControllerInitiated, 0x00061A80,
                            AddressingMode7Bit, "\\_SB.I2C6", 0x00,
                            ResourceConsumer, ,)
                GpioInt (Edge, ActiveHigh, Exclusive, PullDown, 0x0000,
                        "\\_SB.GPO2", 0x00, ResourceConsumer, , )
                { // Pin list
                    0
                }
            })

            Method (_CRS, 0, Serialized)
            {
                Return (RBUF)
            }
        }
    }
}

然后可以将其编译为 AML 二进制格式

$ iasl minnowmax.asl

Intel ACPI Component Architecture
ASL Optimizing Compiler version 20140214-64 [Mar 29 2014]
Copyright (c) 2000 - 2014 Intel Corporation

ASL Input:     minnomax.asl - 30 lines, 614 bytes, 7 keywords
AML Output:    minnowmax.aml - 165 bytes, 6 named objects, 1 executable opcodes

[1] https://www.elinux.org/Minnowboard:MinnowMax#Low_Speed_Expansion_.28Top.29

然后可以使用以下方法之一由内核加载生成的 AML 代码。

从 initrd 加载 ACPI SSDT

此选项允许从 initrd 加载用户定义的 SSDT,当系统不支持 EFI 或没有足够的 EFI 存储时,此选项非常有用。

它的工作方式与基于 initrd 的 ACPI 表覆盖/升级类似:SSDT AML 代码必须放置在第一个未压缩的 initrd 中的“kernel/firmware/acpi”路径下。可以使用多个文件,这将转换为加载多个表。仅允许使用 SSDT 和 OEM 表。有关更多详细信息,请参阅通过 initrd 升级 ACPI 表

这是一个例子

# Add the raw ACPI tables to an uncompressed cpio archive.
# They must be put into a /kernel/firmware/acpi directory inside the
# cpio archive.
# The uncompressed cpio archive must be the first.
# Other, typically compressed cpio archives, must be
# concatenated on top of the uncompressed one.
mkdir -p kernel/firmware/acpi
cp ssdt.aml kernel/firmware/acpi

# Create the uncompressed cpio archive and concatenate the original initrd
# on top:
find kernel | cpio -H newc --create > /boot/instrumented_initrd
cat /boot/initrd >>/boot/instrumented_initrd

从 EFI 变量加载 ACPI SSDT

这是首选方法,当平台支持 EFI 时,因为它允许一种持久的、与操作系统无关的方式来存储用户定义的 SSDT。目前也在进行为 EFI 实现加载用户定义的 SSDT 的支持,并且当该支持到来时,使用此方法将更容易转换为 EFI 加载机制。要启用它,应选择 CONFIG_EFI_CUSTOM_SSDT_OVERLAYS 为 y。

为了从 EFI 变量加载 SSDT,可以使用 "efivar_ssdt=..." 内核命令行参数(名称限制为 16 个字符)。该选项的参数是要使用的变量名。如果存在多个名称相同但供应商 GUID 不同的变量,则将加载所有变量。

为了将 AML 代码存储在 EFI 变量中,可以使用 efivarfs 文件系统。默认情况下,它在所有最新的发行版中都在 /sys/firmware/efi/efivars 中启用和挂载。

在 /sys/firmware/efi/efivars 中创建一个新文件将自动创建一个新的 EFI 变量。更新 /sys/firmware/efi/efivars 中的文件将更新 EFI 变量。请注意,文件名需要特别格式化为“Name-GUID”,并且文件中的前 4 个字节(小端格式)表示 EFI 变量的属性(请参阅 include/linux/efi.h 中的 EFI_VARIABLE_MASK)。写入文件也必须通过一次写入操作完成。

例如,您可以使用以下 bash 脚本使用给定文件的内容创建/更新 EFI 变量

#!/bin/sh -e

while [ -n "$1" ]; do
        case "$1" in
        "-f") filename="$2"; shift;;
        "-g") guid="$2"; shift;;
        *) name="$1";;
        esac
        shift
done

usage()
{
        echo "Syntax: ${0##*/} -f filename [ -g guid ] name"
        exit 1
}

[ -n "$name" -a -f "$filename" ] || usage

EFIVARFS="/sys/firmware/efi/efivars"

[ -d "$EFIVARFS" ] || exit 2

if stat -tf $EFIVARFS | grep -q -v de5e81e4; then
        mount -t efivarfs none $EFIVARFS
fi

# try to pick up an existing GUID
[ -n "$guid" ] || guid=$(find "$EFIVARFS" -name "$name-*" | head -n1 | cut -f2- -d-)

# use a randomly generated GUID
[ -n "$guid" ] || guid="$(cat /proc/sys/kernel/random/uuid)"

# efivarfs expects all of the data in one write
tmp=$(mktemp)
/bin/echo -ne "\007\000\000\000" | cat - $filename > $tmp
dd if=$tmp of="$EFIVARFS/$name-$guid" bs=$(stat -c %s $tmp)
rm $tmp

从 configfs 加载 ACPI SSDT

此选项允许通过 configfs 接口从用户空间加载用户定义的 SSDT。必须选择 CONFIG_ACPI_CONFIGFS 选项,并且必须挂载 configfs。在以下示例中,我们假设 configfs 已挂载在 /sys/kernel/config 中。

可以通过在 /sys/kernel/config/acpi/table 中创建新目录并在 aml 属性中写入 SSDT AML 代码来加载新表

cd /sys/kernel/config/acpi/table
mkdir my_ssdt
cat ~/ssdt.aml > my_ssdt/aml