Arm 系統上的 ACPI

ACPI 可用於設計為遵循 BSA(Arm 基礎系統架構)[0] 和 BBR(Arm 基礎啟動要求)[1] 規範的 Armv8 和 Armv9 系統。BSA 和 BBR 都是公開訪問的文件。Arm 伺服器除了符合 BSA 標準外,還符合 SBSA(伺服器基礎系統架構)[2] 中定義的一組規則。

Arm 核心實現了 ACPI 5.1 或更高版本的簡化硬體模型。規範及其引用的所有外部文件的連結由 UEFI 論壇管理。規範可在 http://www.uefi.org/specifications 獲得,規範引用的文件可透過 http://www.uefi.org/acpi 找到。

如果 Arm 系統不符合 BSA 和 BBR 的要求,或者無法使用所需 ACPI 規範中定義的機制進行描述,則 ACPI 可能不適合該硬體。

雖然上面提到的文件規定了構建行業標準 Arm 系統的要求,但它們也適用於多個作業系統。本文件的目的是僅描述 ACPI 和 Linux 在 Arm 系統上的互動——即 Linux 對 ACPI 的期望以及 ACPI 對 Linux 的期望。

為什麼要在 Arm 上使用 ACPI?

在檢查 ACPI 和 Linux 之間的介面細節之前,瞭解為什麼要使用 ACPI 是很有用的。畢竟,Linux 中已經存在多種用於描述非列舉硬體的技術。在本節中,我們總結了 Grant Likely 的一篇博文 [3],該博文概述了在 Arm 系統上使用 ACPI 的原因。實際上,我們幾乎直接竊取了大部分摘要文字,說實話。

在 Arm 上使用 ACPI 的理由的簡短形式是

  • ACPI 的位元組碼 (AML) 允許平臺對硬體行為進行編碼,而 DT 明確不支援這一點。對於硬體供應商而言,能夠對行為進行編碼是在新硬體上支援作業系統版本的關鍵工具。

  • ACPI 的 OSPM 定義了一個電源管理模型,該模型將平臺允許執行的操作約束到特定模型中,同時仍提供硬體設計的靈活性。

  • 在企業伺服器環境中,ACPI 已經建立了繫結(例如 RAS),這些繫結目前正在生產系統中使用。DT 沒有。此類繫結可以在某個時候在 DT 中定義,但這樣做意味著 Arm 和 x86 最終將在韌體和核心中使用完全不同的程式碼路徑。

  • 選擇一個單一介面來描述平臺和作業系統之間的抽象非常重要。如果硬體供應商想要支援多個作業系統,則無需同時實現 DT 和 ACPI。並且,同意一個單一介面而不是碎片化為每個作業系統介面,可以實現更好的互操作性。

  • 新的 ACPI 治理流程執行良好,Linux 現在與硬體供應商和其他作業系統供應商坐在同一張桌子上。事實上,現在沒有任何理由認為 ACPI 只屬於 Windows,或者 Linux 在這個領域以任何方式次於 Microsoft。將 ACPI 治理移至 UEFI 論壇已大大開放了規範開發流程,並且目前,對 ACPI 進行的大部分更改都是由 Linux 驅動的。

ACPI 使用的關鍵是支援模型。對於一般的伺服器,硬體行為的責任不能僅僅是核心的領域,而必須在平臺和核心之間進行劃分,以便隨著時間的推移進行有序的更改。ACPI 使作業系統無需瞭解硬體的所有細微細節,因此作業系統無需單獨移植到每個裝置。它允許硬體供應商負責電源管理行為,而無需依賴不受其控制的作業系統釋出週期。

ACPI 也很重要,因為硬體和作業系統供應商已經制定了支援通用計算生態系統的機制。基礎設施已到位,繫結已到位,流程已到位。當與垂直整合裝置一起使用時,DT 確實滿足了 Linux 的需求,但是沒有好的流程來支援伺服器供應商的需求。Linux 可能會透過 DT 實現這一目標,但這樣做實際上只是重複了已經有效的東西。ACPI 已經滿足了硬體供應商的需求,Microsoft 不會與 DT 合作,並且硬體供應商最終仍將提供兩個完全獨立的韌體介面——一個用於 Linux,一個用於 Windows。

核心相容性

ACPI 的主要動機之一是標準化,並使用它為 Linux 核心提供向後相容性。在伺服器市場中,軟體和硬體通常會長期使用。ACPI 允許核心和韌體就一個一致的抽象達成一致,即使硬體或軟體發生變化,也可以隨著時間的推移進行維護。只要支援該抽象,就可以更新系統,而不必更換核心。

當 Linux 驅動程式或子系統首次使用 ACPI 實現時,根據定義,它最終需要特定版本的 ACPI 規範——其基線。即使可能不是最佳的,ACPI 韌體也必須繼續使用最早的核心版本,該版本首先提供對該基線版本的 ACPI 的支援。可能需要額外的驅動程式,但是新增新功能(例如,CPU 電源管理)不應破壞舊核心版本。此外,ACPI 韌體還必須與最新版本的核心一起使用。

與裝置樹的關係

Arm 的驅動程式和子系統中的 ACPI 支援在編譯時絕不應與 DT 支援互斥。

在啟動時,核心將僅使用一種描述方法,具體取決於從引導載入程式傳遞的引數(包括核心引導引數)。

無論使用 DT 還是 ACPI,核心始終必須能夠使用任一方案啟動(在編譯時啟用這兩種方案的核心中)。

使用 ACPI 表啟動

在 Arm 上將 ACPI 表傳遞給核心的唯一定義方法是透過 UEFI 系統配置表。為了明確起見,這意味著 ACPI 僅在透過 UEFI 啟動的平臺上受支援。

當 Arm 系統啟動時,它可以具有 DT 資訊、ACPI 表,或者在某些非常不尋常的情況下,兩者都有。如果不使用命令列引數,核心將嘗試使用 DT 進行裝置列舉;如果不存在 DT,核心將嘗試使用 ACPI 表,但前提是它們存在。如果兩者都不可用,核心將無法啟動。如果在命令列上使用 acpi=force,核心將首先嚐試使用 ACPI 表,但如果不存在 ACPI 表,則回退到 DT。基本思想是,除非絕對沒有其他選擇,否則核心不會啟動失敗。

可以透過在核心命令列上傳遞 acpi=off 來停用 ACPI 表的處理;這是預設行為。

為了使核心載入和使用 ACPI 表,UEFI 實現必須將 ACPI_20_TABLE_GUID 設定為指向 RSDP 表(帶有 ACPI 簽名“RSD PTR “的表)。如果此指標不正確並且使用了 acpi=force,核心將停用 ACPI 並嘗試使用 DT 啟動;實際上,核心已確定此時不存在 ACPI 表。

如果指向 RSDP 表的指標正確,則 ACPI 核心會將該表對映到核心中,使用 UEFI 提供的地址。

然後,ACPI 核心將透過使用 RSDP 表中的地址找到 XSDT(擴充套件系統描述表)來查詢並對映所有其他提供的 ACPI 表。XSDT 反過來提供系統韌體提供的所有其他 ACPI 表的地址;然後,ACPI 核心將遍歷此表並對映列出的表。

ACPI 核心將忽略任何提供的 RSDT(根系統描述表)。RSDT 已被棄用,並且由於它們僅允許 32 位地址,因此在 arm64 上被忽略。

此外,ACPI 核心將僅使用 FADT(固定 ACPI 描述表)中的 64 位地址欄位。FADT 中的任何 32 位地址欄位都將在 arm64 上被忽略。

ACPI 核心將在 arm64 上強制執行硬體簡化模式(請參閱 ACPI 6.1 規範的第 4.1 節)。這樣做可以使 ACPI 核心執行更簡單的程式碼,因為它不再需要提供對來自其他架構的舊硬體的支援。任何不用於硬體簡化模式的欄位都必須設定為零。

為了使 ACPI 核心正常執行,並反過來提供核心配置裝置所需的資訊,它希望找到以下表(所有節號均指 ACPI 6.5 規範)

  • RSDP(根系統描述指標),第 5.2.5 節

  • XSDT(擴充套件系統描述表),第 5.2.8 節

  • FADT(固定 ACPI 描述表),第 5.2.9 節

  • DSDT(區分系統描述表),第 5.2.11.1 節

  • MADT(多 APIC 描述表),第 5.2.12 節

  • GTDT(通用計時器描述表),第 5.2.24 節

  • PPTT(處理器屬性拓撲表),第 5.2.30 節

  • DBG2(除錯埠表 2),第 5.2.6 節,特別是表 5-6。

  • APMT(Arm 效能監視單元表),第 5.2.6 節,特別是表 5-6。

  • AGDI(Arm 通用診斷轉儲和重置裝置介面表),第 5.2.6 節,特別是表 5-6。

  • 如果支援 PCI,則為 MCFG(記憶體對映配置表),第 5.2.6 節,特別是表 5-6。

  • 如果支援在沒有 console=<device> 核心引數的情況下啟動,則為 SPCR(序列埠控制檯重定向表),第 5.2.6 節,特別是表 5-6。

  • 如果需要描述 I/O 拓撲、SMMU 和 GIC ITS,則為 IORT(輸入輸出重新對映表,第 5.2.6 節,特別是表 5-6)。

  • 如果支援 NUMA,則需要以下表

    • SRAT(系統資源關聯表),第 5.2.16 節

    • SLIT(系統本地距離資訊表),第 5.2.17 節

  • 如果支援 NUMA,並且系統包含異構記憶體,則為 HMAT(異構記憶體屬性表),第 5.2.28 節。

  • 如果需要 ACPI 平臺錯誤介面,則有條件地需要以下表

    • BERT(啟動錯誤記錄表,第 18.3.1 節)

    • EINJ(錯誤注入表,第 18.6.1 節)

    • ERST(錯誤記錄序列化表,第 18.5 節)

    • HEST(硬體錯誤源表,第 18.3.2 節)

    • SDEI(軟體委派異常介面表,第 5.2.6 節,特別是表 5-6)

    • AEST(Arm 錯誤源表,第 5.2.6 節,特別是表 5-6)

    • RAS2(ACPI RAS2 功能表,第 5.2.21 節)

  • 如果系統包含使用 PCC 通道的控制器,則為 PCCT(平臺通訊通道表),第 14.1 節

  • 如果系統包含一個用於捕獲板級系統狀態的控制器,並透過 PCC 與主機通訊,則為 PDTT(平臺除錯觸發表),第 5.2.29 節。

  • 如果支援 NVDIMM,則為 NFIT(NVDIMM 韌體介面表),第 5.2.26 節

  • 如果存在影片幀緩衝區,則為 BGRT(啟動圖形資源表),第 5.2.23 節

  • 如果實現了 IPMI,則為 SPMI(伺服器平臺管理介面),第 5.2.6 節,特別是表 5-6。

  • 如果系統包含 CXL 主機橋,則為 CEDT(CXL 早期發現表),第 5.2.6 節,特別是表 5-6。

  • 如果系統支援 MPAM,則為 MPAM(記憶體分割槽和監視表),第 5.2.6 節,特別是表 5-6。

  • 如果系統缺少持久儲存,則為 IBFT(ISCSI 啟動韌體表),第 5.2.6 節,特別是表 5-6。

如果以上表格並非全部存在,則核心可能無法正常啟動,因為它可能無法配置所有可用的裝置。此表格列表並非旨在包含所有內容;在某些環境中,可能需要其他表格(例如,來自第 18 節的任何 APEI 表格)來支援特定功能。

ACPI 檢測

驅動程式應透過檢查 ACPI_HANDLE 是否為 null 值、檢查 .of_node 或裝置結構中的其他資訊來確定其 probe() 型別。這在“驅動程式建議”部分中進行了更詳細的說明。

在非驅動程式程式碼中,如果需要在執行時檢測 ACPI 的存在,請檢查 acpi_disabled 的值。如果未設定 CONFIG_ACPI,則 acpi_disabled 將始終為 1。

裝置列舉

ACPI 中的裝置描述應使用標準認可的 ACPI 介面。與通常透過同一裝置的裝置樹描述提供的資訊相比,這些介面可能包含的資訊較少。這也是 ACPI 可能有用的原因之一——驅動程式考慮到它可能具有關於裝置的較少詳細資訊,並使用合理的預設值代替。如果在驅動程式中正確完成,硬體可以隨著時間的推移進行更改和改進,而無需更改驅動程式。

時鐘提供了一個很好的例子。在 DT 中,需要指定時鐘,並且驅動程式需要考慮它們。在 ACPI 中,假設 UEFI 會將裝置置於合理的預設狀態,包括任何時鐘設定。如果由於某種原因驅動程式需要更改時鐘值,則可以在 ACPI 方法中完成此操作;驅動程式需要做的就是呼叫該方法,而不必關心該方法需要做什麼才能更改時鐘。然後,可以透過更改 ACPI 方法的功能而不是驅動程式來隨著時間的推移更改硬體。

在 DT 中,驅動程式設定時鐘所需的引數(如上面的示例中)被稱為“繫結”;在 ACPI 中,這些被稱為“裝置屬性”,並透過 _DSD 物件提供給驅動程式。

ACPI 表使用一種稱為 ASL 的形式語言進行描述,即 ACPI 源語言(規範的第 19 節)。這意味著始終有多種方法來描述同一件事——包括裝置屬性。例如,裝置屬性可以使用看起來像這樣的 ASL 構造:Name(KEY0, “value0”)。然後,ACPI 裝置驅動程式將透過評估 KEY0 物件來檢索屬性的值。但是,以這種方式使用 Name() 存在多個問題:(1) 與 DT 不同,ACPI 將名稱 (“KEY0”) 限制為四個字元;(2) 沒有行業範圍的登錄檔來維護名稱列表,從而最大限度地減少重用;(3) 也沒有屬性值 (“value0”) 定義的登錄檔,再次使重用變得困難;(4) 隨著新硬體的推出,如何保持向後相容性?建立 _DSD 方法正是為了解決這些問題;Linux 驅動程式應始終將 _DSD 方法用於裝置屬性,而不是其他任何方法。

_DSM 物件(ACPI 第 9.14.1 節)也可用於將裝置屬性傳達給驅動程式。僅當 _DSD 無法表示所需的資料,並且無法為 _DSD 物件建立新的 UUID 時,Linux 驅動程式才應期望使用它。請注意,與 _DSD 相比,對 _DSM 的使用甚至更少受到監管。由於這個原因,依賴於 _DSM 物件內容的驅動程式將更難以隨著時間的推移進行維護;截至本文撰寫之時,_DSM 的使用是造成相當多韌體問題的原因,因此不建議使用。

驅動程式應僅在 _DSD 物件中查詢裝置屬性;_DSD 物件在 ACPI 規範的 6.2.5 節中進行了描述,但這僅描述瞭如何定義透過 _DSD 返回的物件的結構,以及如何透過特定的 UUID 定義特定的資料結構。Linux 應僅使用 _DSD 裝置屬性 UUID [4]

  • UUID:daffd814-6eba-4d8c-8a91-bc9bbf4aa301

可以透過建立拉取請求到 [4] 來註冊通用裝置屬性,以便它們可以在所有支援 ACPI 的作業系統中使用。尚未在 UEFI 論壇註冊的裝置屬性可以用作“uefi-”通用屬性。

在建立新的裝置屬性之前,請檢查以確保它們之前沒有定義過,並且已在 Linux 核心文件中註冊為 DT 繫結,或者在 UEFI 論壇中註冊為裝置屬性。雖然我們不想簡單地將所有 DT 繫結移動到 ACPI 裝置屬性中,但我們可以從先前定義的繫結中學習。

如果需要定義新的裝置屬性,或者如果合成繫結的定義以便可以在任何韌體中使用是有意義的,則裝置驅動程式的 DT 繫結和 ACPI 裝置屬性都具有審查流程。同時使用它們。當驅動程式本身提交到 Linux 郵件列表進行審查時,必須同時提交所需的裝置屬性定義。如果不提供其定義,則不支援 ACPI 並使用裝置屬性的驅動程式將不被視為完整。一旦裝置屬性被 Linux 社群接受,它必須在 UEFI 論壇 [4] 註冊,該論壇將再次審查它在登錄檔中的一致性。這可能需要迭代。但是,UEFI 論壇將始終是裝置屬性定義的規範站點。

通知 UEFI 論壇有意註冊以前未使用的裝置屬性名稱作為保留名稱以供以後使用的一種手段可能是有意義的。其他作業系統供應商也將提交註冊請求,這可能有助於簡化流程。

完成註冊和審查後,核心提供了一個介面,用於以獨立於使用 DT 還是 ACPI 的方式查詢裝置屬性。應使用此 API [5];它可以消除驅動程式探測函式中的一些程式碼路徑重複,並防止 DT 繫結和 ACPI 裝置屬性之間的差異。

可程式設計電源控制資源

可程式設計電源控制資源包括電壓/電流提供器(穩壓器)和時鐘源等資源。

使用 ACPI 時,不應使用核心時鐘和穩壓器框架。

核心假定這些資源的電源控制由電源資源物件表示(ACPI 第 7.1 節)。然後,ACPI 核心將在需要時正確地啟用和停用資源。為了使其工作,ACPI 假定每個裝置都定義了 D 狀態,並且這些狀態可以透過可選的 ACPI 方法 _PS0、_PS1、_PS2 和 _PS3 進行控制;在 ACPI 中,_PS0 是呼叫以完全開啟裝置的方法,而 _PS3 是用於完全關閉裝置的方法。

使用這些電源資源有兩種選擇。他們可以

  • 在進入電源狀態 Dx 時呼叫的 _PSx 方法中進行管理。

  • 單獨宣告為電源資源,並具有自己的 _ON 和 _OFF 方法。然後,透過 _PRx 將它們繫結回特定裝置的 D 狀態,_PRx 指定裝置在 Dx 中需要哪些電源資源才能開啟。然後,核心跟蹤使用電源資源的裝置數量,並根據需要呼叫 _ON/_OFF。

核心 ACPI 程式碼還將假定 _PSx 方法遵循此類方法的正常 ACPI 規則

  • 如果實現了 _PS0 或 _PS3,則還必須實現另一種方法。

  • 如果裝置在開啟時需要使用或設定電源資源,ASL 應安排使用 _PS0 方法分配/啟用該資源。

  • 在 _PS0 方法中分配或啟用的資源應在 _PS3 方法中停用或取消分配。

  • 韌體會將資源置於合理狀態,然後再將控制權交給核心。

_PSx 方法中的此類程式碼當然會非常特定於平臺。但是,這允許驅動程式提取操作裝置的介面,並避免必須從 ACPI 表中讀取特殊的非標準值。此外,提取這些資源的使用允許硬體隨著時間的推移進行更改,而無需更新驅動程式。

時鐘

ACPI 假定時鐘由韌體(在本例中為 UEFI)初始化為某個工作值,然後再將控制權交給核心。這對於 UART 或 SoC 驅動的 LCD 顯示器等裝置有影響。

當核心啟動時,假定時鐘已設定為合理的工作值。如果由於某種原因需要更改頻率(例如,為了電源管理而進行限制),則裝置驅動程式應期望該過程被提取到可以呼叫的某些 ACPI 方法中(請參閱 ACPI 規範以獲取有關要期望的標準方法的更多建議)。唯一的例外是 CPU 時鐘,其中 CPPC 提供了比 ACPI 方法更豐富的介面。如果未設定時鐘,則 Linux 無法直接控制它們。

如果 SoC 供應商想要提供對系統時鐘的精細控制,他們可以透過提供可以由 Linux 驅動程式呼叫的 ACPI 方法來實現。但是,不建議這樣做,並且 Linux 驅動程式不應使用此類方法,即使提供了這些方法。此類方法當前未在 ACPI 規範中標準化,並且使用它們可能會將核心繫結到非常特定的 SoC,或者將 SoC 繫結到非常特定版本的核心,這都是我們試圖避免的。

驅動程式建議

在為驅動程式新增 ACPI 支援時,請勿刪除任何 DT 處理。同一裝置可以在許多不同的系統上使用。

嘗試構建驅動程式,使其由資料驅動。也就是說,基於預設值和驅動程式探測函式必須發現的其他任何內容,設定一個包含內部每個裝置狀態的結構。然後,讓驅動程式的其餘部分根據該結構的內容進行操作。這樣做應該允許 ACPI 和 DT 功能之間的最大差異保持在探測函式本地,而不是分散在整個驅動程式中。例如

static int device_probe_dt(struct platform_device *pdev)
{
       /* DT specific functionality */
       ...
}

static int device_probe_acpi(struct platform_device *pdev)
{
       /* ACPI specific functionality */
       ...
}

static int device_probe(struct platform_device *pdev)
{
       ...
       struct device_node node = pdev->dev.of_node;
       ...

       if (node)
               ret = device_probe_dt(pdev);
       else if (ACPI_HANDLE(&pdev->dev))
               ret = device_probe_acpi(pdev);
       else
               /* other initialization */
               ...
       /* Continue with any generic probe operations */
       ...
}

將 MODULE_DEVICE_TABLE 條目儲存在驅動程式中,以清楚地瞭解驅動程式探測的不同名稱,無論是來自 DT 還是來自 ACPI

static struct of_device_id virtio_mmio_match[] = {
        { .compatible = "virtio,mmio", },
        { }
};
MODULE_DEVICE_TABLE(of, virtio_mmio_match);

static const struct acpi_device_id virtio_mmio_acpi_match[] = {
        { "LNRO0005", },
        { }
};
MODULE_DEVICE_TABLE(acpi, virtio_mmio_acpi_match);

ASWG

ACPI 規範會定期更改。例如,在 2014 年,釋出了 5.1 版本,並且基本完成了 6.0 版本,其中大部分更改是由 Arm 特定的要求驅動的。提出的更改將在 ASWG(ACPI 規範工作組)中進行介紹和討論,該工作組是 UEFI 論壇的一部分。ACPI 規範的當前版本是 2022 年 8 月釋出的 6.5 版本。

該小組的參與權向所有 UEFI 成員開放。有關小組會員資格的詳細資訊,請參閱 http://www.uefi.org/workinggroup

Arm ACPI 核心程式碼的目的是儘可能地遵循 ACPI 規範,並且僅實現符合 UEFI ASWG 釋出的標準的功能。實際上,會有供應商提供錯誤的 ACPI 表或以某種方式違反標準。如果是由於錯誤,則可能需要怪癖和修復程式,但如果可能,將避免使用。如果 ACPI 中缺少阻止其在平臺上使用的功能,則應將 ECR(工程變更請求)提交給 ASWG 並透過正常的批准流程;對於那些不是 UEFI 成員的人,Linux 社群的許多其他成員都是,並且很可能願意協助提交 ECR。

Linux 程式碼

包含在 Linux 原始碼中的特定於 Arm 的 Linux 的各個專案如下

ACPI_OS_NAME

此宏定義了當 ACPI 方法呼叫 _OS 方法時要返回的字串。在 Arm 系統上,預設情況下,此宏將為“Linux”。命令列引數 acpi_os=<string> 可用於將其設定為其他值。例如,其他架構的預設值為“Microsoft Windows NT”。

ACPI 物件

有關 ACPI 表和物件的詳細期望列在檔案 ACPI 表 中。

參考

[0] https://developer.arm.com/documentation/den0094/latest

文件 Arm-DEN-0094:“Arm 基礎系統架構”,版本 1.0C,日期為 2022 年 10 月 6 日

[1] https://developer.arm.com/documentation/den0044/latest

文件 Arm-DEN-0044:“Arm 基礎啟動要求”,版本 2.0G,日期為 2022 年 4 月 15 日

[2] https://developer.arm.com/documentation/den0029/latest

文件 Arm-DEN-0029:“Arm 伺服器基礎系統架構”,版本 7.1,日期為 2022 年 10 月 6 日

[3] http://www.secretlab.ca/archives/151,

2015年1月10日,版權 (c) 2015, Linaro Ltd.,作者:Grant Likely。

[4] _DSD(裝置特定資料)實施指南

https://github.com/UEFI/DSD-Guide/blob/main/dsd-guide.pdf

[5] 統一裝置的核心程式碼

屬性介面可在 include/linux/property.h 和 drivers/base/property.c 中找到。

作者