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