SSDT 叠加层

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

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

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

例如,要在 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 中创建新目录并将 SSDT AML 代码写入 aml 属性来加载新表:

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