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