【CSDN 編者按】自去年蘋果自研 M1 芯片發布之后,激發了無數用戶的體驗熱情,與此同時,也吸引大批開發者在 M1 上開啟探索模式,其中,國外一位資深作業系統移植專家 Hector Martin 發起了一項名為「Asahi Linux」專案,通過眾籌的方式為蘋果 M1 系列新機移植 Linux 系統,
當前,這一專案自啟動至今已有 2 個月的時間,Martin 也在 Asahi Linux 官網上最新發布了移植進展及首份報告,面對整個嘗試的程序,該份報告指出,“讓 M1 支持 Linux 真的太難了!”
接下來,讓我們將共同通過這份報告,快速了解移植 Linux 的痛點所在!
作者 | AsahiLinux.org
譯者 | 彎月
出品 | CSDN(ID:CSDNnews)
以下為譯文:
歡迎各位閱讀我們的第一份《Asahi Linux 進度報告》!文本將向你播報該專案的最新進展的,
支持新型的 Linux 系統芯片絕非易事!我們希望通過本文,各位能夠了解為了讓 Linux 在新設備上運行,我們在幕后付出的艱辛,
術語說明
在這篇報告中,我們會提到一系列的術語:AArch64、ARM64 和 ARMv8-A,
-
AArch64 指的是 64 位 ARM 體系結構指令集;
-
ARM64 是 Linux 為 64 位 ARM 提供的支持;
-
ARMv8-A 是包含 AArch64 的 ARM CPU 體系結構規范,
這些術語的含義略有不同,但是在文本中,你可以將它們全部理解為“ 64位ARM”,

專案的起始
Asahi Linux 專案于今年年初正式啟動,但當時我們都在等待一個關鍵的部分:在蘋果芯片系統上引導其他內核的支持,雖然該功能早已進入開發檔案,而且大部分都已實作,但還缺少最后一個關鍵部分:對于 kmutil configure-boot命令的支持,只有通過這個命令才能安裝非蘋果內核,但這個問題未能阻止我們前進,為了將作業系統移植到一個沒有檔案記錄的平臺,第一步要做的就是建立檔案記錄!
搭載蘋果芯片 Macs 的啟動方式與傳統 PC 完全不同,它的作業方式更類似于嵌入式平臺(比如安卓手機,iOS 設備等),但是引入了許多特別的機制,然而,蘋果已經采取了一些措施,讓啟動程序更貼近英特爾芯片的 Mac 作業系統,因此人們對實際的運轉方式充滿了困惑,例如,根據傳統的經驗來看,搭載蘋果芯片的 Mac根本不能通過外的存盤啟動,搭載蘋果芯片 Mac 的引導程式也無法顯示圖形用戶界面,并且“引導程式選擇器”實際上是一個全屏的 macOS 應用,而不是引導程式的一部分,
因此,為了在這些計算機上運行自己的內核,首先我們必須確認啟動程序的作業方式,內部 SSD 上磁區和卷的布局方式,并找出與 PC 的區別,該檔案不僅對我們的專案有幫助,而且也可以作為希望更好地了解計算機作業原理的所有 macOS 用戶的參考檔案,
2021年2月版的《蘋果平臺安全指南》(https://support.apple.com/en-ca/guide/security/welcome/web)中記載了部分功能及其基本原理,

連接兩個世界的橋梁
搭載蘋果芯片的 Mac 啟動程序沒有遵循任何現有標準,它是定制的蘋果機制,從 iOS 設備的早期階段慢慢發展起來的,
而在蘋果之外,64 位 ARM 世界基本上可以分成兩大互相競爭的標準:UEFI + ACPI(主要在運行Windows或Linux的服務器上使用)和 ARM64 Linux 引導協議+ 設備樹(在小型系統上使用,并得到了U-Boot等的支持),我們需要為Asahi Linux 選擇其中一種標準,并找到一種方法在蘋果和我們的世界之間架起橋梁,
UEFI 和 ACPI 是非常復雜的龐然大物,通常僅適用于大型 ARM 系統,這些標準主要由 UEFII 論壇的委員會控制,x86 PC 世界比較單一,但 ARM 世界則極為多樣化,系統芯片擁有各種各樣的設計,為的是滿足其上硬體的不同要求,因此,如果想增加對新 SoC 的支持,則必須修改這些標準,給那些特殊的硬體添加“系結”,對于 ACPI 而言,這項作業既昂貴又緩慢,這就是為什么 ACPI 幾乎從來不在 Windows 以外的小型嵌入式系統上使用,對于我們來說,這個選擇行不通,
各種各樣的小型嵌入式 ARM Linux 系統幾乎都采用了設備樹(DeviceTree)標準,比如大多數安卓設備的啟動都采用了這種方式,設備樹比 ACPI 簡單得多,因為設備樹純粹是一堆描述硬體的資料,而 ACPI 表則結合了資料和代碼,如今,設備樹系結的權威是 Linux 內核樹內部維護的檔案,這意味著我們可以在撰寫Linux驅動程式本身的同時,修改這些標準,因此,Asahi Linux 的啟動程序也采用了這種模型,
有意思的是,蘋果針對蘋果芯片的設備建立了蘋果版的設備樹,名叫蘋果設備樹(Apple Device Tree)!這是因為蘋果和開放的設備樹標準都建立在開放韌體規范(包括舊款 Mac 在內的許多 PowerPC 系統都采用了該規范)之上,不幸的是,盡管這意味著 ADT 對于嵌入式 Linux 開發人員來說并不陌生,但我們還是不能直接使用它們,原因在于二進制格式不同,而且如果沒有關于資料含義的高級資訊,兩種格式之間就無法自動轉換,而在規范之上,各個設備的實際系結完全不一樣,雖然 Linux 和 macOS 在 PowerPC Mac 上的作業方式相同,并且可以兼容,但 Linux 和蘋果在 ARM 領域已經分別發展了十多年,試圖統一蘋果和 Linux 處理設備樹的方式將是一場噩夢,
為了讓蘋果使用設備樹,我們正在開發 m1n1,這是一款蘋果芯片電腦的引導程式,它的目標是盡可能多地處理“蘋果風格”的東西,減輕 Linux 或其他下游產品的負擔,
你可以將 m1n1 添加到 Linux 內核的前面(對于最簡單的固定內核,只需要運行cat m1n1.macho initrd.bin devicetree.dtb Image.gz > m1n1-kernel.macho即可),然后使用蘋果的工具 kmutil 將其安裝到 Mac 上,它就會負責啟動 Linux 所需的一切處理,使用 m1n1 引導 Linux 的大致程序如下:
-
初始化主 CPU,并應用 chicken bit 設定,使其正常作業,
-
讀取蘋果引導加載程式 iBoot 提供給它的引導資訊:其中包括可用的記憶體量以及記憶體幀緩沖區(螢屏上顯示的視頻的記憶體)的地址,
-
初始化記憶體管理單元,為了能夠使用 CPU 快取,這一步是必需的,否則,一切運行速度都會非常慢,
-
在螢屏上顯示 Asahi Linux 的標志,代替蘋果的標志,
-
禁用 watchdog timer,如果沒有這一步,Mac 會在大約一分鐘后自發重啟,因為它以為啟動程序被卡住了,
-
找出需要啟動的程式:Linux 內核、設備樹以及(可選的)包含啟動時應用程式的 initramfs ramdisk(如果已將它們附加到啟動時的話),
-
初始化所有其他 CPU 核心,并應用必要的 chicken bit,然后讓它們在“旋轉表”中等待 Linux 的接管,
-
從蘋果的設備樹中獲取資訊,并修改設備樹模板,使二者匹配,這一步是為了修改不同的計算機以及蘋果 iBoot 韌體不同版本的設定,例如內存大小、有關幀緩沖區的資訊、初始化 Linux 亂數生成器的種子等,此外, m1n1 還添加了一些自己的資訊,例如旋轉表的詳細資訊以及內核的命令列引數(視情況而定),
-
跳轉到 Linux,或下一步,
“旋轉表”(spin-table)是 ARM 版 Linux 在設備樹的世界中啟動額外的 CPU核心的兩種標準之一,不依賴于平臺特定的驅動的標準方法有兩種,所有平臺都要使用兩者之一,最簡單的一種叫做旋轉表,其做法是讓引導程式事先啟用所有CPU 核心,然后讓它們在一個回圈中等待(叫做“旋轉”),為了從回圈中釋放CPU,Linux 需要向記憶體寫入一個值,告訴 CPU 從何處跳轉到內核,對于簡單的平臺來說這完全沒問題,唯一的限制就是沒有辦法完全停止 CPU,因為從引導程式中接管 CPU 是一次性的,不過可以通過其他機制讓 CPU 進入各種省電模式,我們目前采用了這種方式,有可能以后也會一直延續下去,
另一種方法叫做“PSCI”,這是一個 ARM 標準,是系統韌體提供的服務,即使在Linux運行時,也可以利用它同時控制所有 CPU,通常,該操作需要運行在 “EL3”(即安全韌體,又稱TrustZone)上的代碼來實作,或者通過運行在“EL2”上的虛擬機監控程式來實作,而作業系統通常運行在 EL1 上,但是,在ARMv8-A 的 CPU 中,EL3和EL2都是可選的,而且事實證明 M1 并不支持EL3,M1支持EL2,但是我們希望能在Linux下運行虛擬機,這就要求 Linux 本身需要運行在 EL2 中,因此沒辦法在 EL2 中運行一個監控程式,這就意味著我們現在還不能使用 PSCI,因為 PSCI 的標準介面不滿足我們的需求,以后也許能夠出現其他標準方法,也許,只有采用其他方法,才能支持整個系統的睡眠功能,盡管如果細粒度的電源管理足夠好的話,我們也許不需要“真正”的全系統睡眠模式,就能獲得不錯的待機時間(現代設備對于更細粒度的睡眠模式的支持非常好),不過這個領域仍然在發展,所以只能拭目以待了,
雖然我說過我們要使用設備樹,但這并不意味著我們不能使用 UEFI!ARM64系統能夠同時使用 UEFI 和設備樹進行引導,而且只有這樣做,才能像 PC 那樣通過 GRUB 等引導程式和通用的流程安裝和升級內核,但是 m1n1 并不支持這樣做,那么怎么辦呢?幸好還有其他途徑:U-Boot,U-Boot 可以像 Linux 內核一樣引導,所以只需從 m1n1 中引導 U-Boot,然后 U-Boot 就可以為GRUB 和 Linux 提供良好的 UEFI 環境,
因此,最終 Asahi Linux 的引導鏈大致如下:
m1n1 → U-Boot → GRUB → Linux
結合蘋果特有的引導鏈,整個引導程序大致如下:
-
在冷啟動時,M1芯片內的 SecureROM 啟動,并從NOR閃存中加載iBoot1,
-
iBoot1 讀取內置 SSD 中的引導配置,驗證系統引導策略,然后選擇一個“作業系統”進行引導,在我們看來,Asahi Linux / m1n1 對于 iBoot1而言就像一個作業系統磁區,
-
iBoot2(作業系統引導程式,它需要位于被引導的作業系統磁區內)加載韌體供內置設備使用,設定蘋果設備樹(ADT),并引導一個 Mach-O 內核(對于我們,就是m1n1),
-
m1n1 決議 ADT,配置更多設備,讓整個環境更像 Linux,然后設定 FDT(展平后的設備樹,即二進制格式的設備樹),然后引導 U-Boot,
-
U-Boot(其驅動程式位于內置SSD中)讀取其配置和下一階段的代碼,然后提供UEFI服務(其中包括轉發來自m1n1的設備樹),
-
GRUB作為標準的UEFI應用,從磁盤磁區中引導,就像 PC 上的 GRUB 一樣,有了 GRUB,Linux 發行版就可以通過慣常的方式,使用 grub-mkconfig 和 /etc/default/grub 等管理內核,
-
最后,引導 Linux 內核,所需的資訊由從 m1n1 傳過來的設備樹提供,
對于習慣了 PC 的人來說,這個程序可能有點不可思議,但在嵌入式系統中,這種很長的引導鏈是十分常見的(而且實際上,即使在普通的 PC 上,UEFI 也包含多個階段,只不過最終用戶看不到而已),例如,DragonBoard 410c(一款基于高通的平臺)的引導鏈可能如下:
PBL→SBL→QSEE→QHEE→LK→U-Boot→GRUB→Linux
注意,我們沒辦法替換 iBoot2(它需要蘋果的簽名),但最終用戶的安裝程序會自動設定一個最小化的“macOS”,其中包含 iBoot2 和所有必須的支持檔案,為的就是解決這個問題,這些足夠讓蘋果的引導程序將其識別為可引導的OS(只不過沒有真正的macOS內核和檔案系統),我們還沒有實作安裝程式,所以目前開發人員只能通過先整安裝 macOS,再替換內核的方式來嘗試 m1n1和LInux,我們撰寫了一個手把手的快速入門指南(https://github.com/AsahiLinux/docs/wiki/Developer-Quickstart),供想嘗鮮的人使用,
目前,我們主要的開發作業是從直接 m1n1 中加載 Linux,不過 Mark Kettenis 在負責 U-Boot 和 OpenBSD 的支持作業,
但是 m1n1 不僅僅是運行 Linux,實際上,它本身甚至不是引導程式!

處理硬體問題
m1n1 誕生于 mini,后者是我為任天堂 Wii 的安全 CPU 撰寫的一個最小化環境,它很適合拿來做各種試驗,以及作為 BootMii 的后端,如果你手里有Wii,而且還聽說過 BootMii,那么當你在 BootMii 的選單中時,ARM CPU上運行的就是 mini,
那么,這跟蘋果芯片上的引導程式有什么關系呢?實際上,mini 只不過是在32位裸金屬 ARM 系統上運行的一個非常簡單的軟體,不包含任何外部庫和依賴,因此,它非常適合構建裸金屬代碼,于是我們將其移植到了 AArch64 和蘋果芯片上,并改名為 m1n1,但更重要的是,mini 和 m1n1 都有一個秘密武器:由于 mini 作為韌體在一個單獨的處理器上運行,而這個處理器需要主 CPU 負責控制,而且根據以前針對 Wii 的硬體研究成果,mini 內置了一個 RPC 代理,可以通過串口訪問,這就意味著你可以從一臺開發計算機上對 mini 和m1n1進行“遠程控制”,甚至可以從互動式的shell中進行(https://github.com/AsahiLinux/docs/wiki/Developer-Quickstart#playground-shell),所以更恰當的描述是,m1n1 是一個硬體實驗工具,恰好能作為 Linux 引導程式使用,
所以說,這個平臺特別適合硬體學習,而且適合尋找蘋果的私有特征,例如,
-
這段腳本(https://github.com/AsahiLinux/m1n1/blob/main/proxyclient/fptest.py)測驗了一個特殊的蘋果特性:給 CPU 添加一些 x86 專有的浮點配置位元,用于加速 Rosetta x86 模擬,
-
這個腳本(https://github.com/AsahiLinux/m1n1/blob/main/proxyclient/find_all_regs.py)能夠搜索所有蘋果定制的 CPU 暫存器,并輸出它們的值和訪問限制,
-
這個腳本(https://github.com/AsahiLinux/m1n1/blob/main/proxyclient/hacr_trap_bits.py)能夠自動找出怎樣通過蘋果專有的監控程式配置暫存器來實施這些訪問限制,
-
當然,還有這個腳本(https://github.com/AsahiLinux/m1n1/blob/main/proxyclient/linux.py)能夠引導Linux內核,并通過串口輸出,
將一臺 M1 Mac Mini 引導至 m1n1 需要大約 7 秒,而且所有這些腳本都可以互動式運行,無需重啟(除非你把機器搞崩潰了),m1n1 還能加載自身,所以 m1n1 的開發周期非常快:只需用 kmutil 安裝一次 m1n1,以后重啟后只需加載最新的 m1n1 即可,
我們使用 m1n1 為蘋果的自定義 ARM 指令集、蘋果專用的系統暫存器以及蘋果中斷控制器等硬體建立了檔案,
以后,我們會繼續給 m1n1 添加更多特性,讓它成為更強大的研究工具,其中一個特別激動人心的目標就是,將其變成一個非常薄的虛擬機監控程式,能夠啟動 macOS,并攔截 macOS 對于 M1 硬體的訪問,如此一來,我們無需反編譯,就能調查蘋果的驅動程式的作業方式,還能通過合法的渠道進行調查,而且比跟蹤復雜的私有驅動程式的代碼效率高很多,一些人可能知道這種方法,因為之前 nouveau 就成功地通過此方法,對 NVidia 的 GPU 進行了逆向工程,但當時他們使用的是 Linux 驅動程式,而且只修改了內核,沒有采用虛擬機監控程式,
但是等一下,這一切都需要串口,但是 M1 的 Mac 哪兒有串口?好問題!

UART 登場!
對于新系統的底層開發,串口幾乎是不可避免的,串口(有時也稱為UART埠)是最簡單的通信硬體,對于底層除錯工具來說非常方便,通過串口發送訊息只需要幾條 CPU 指令,所以我們在非常早期就可以建立串口通信,作為開發的文本終端使用,
當然,現代 PC 曾經有過 RS-232 串口,但那些都是過去了,在許多嵌入式系統(如絕大多數家用路由器)的內部依然有低電壓串口,但需要拆開外殼才能連接,或者是直接位于主板上的測驗點,那么 M1 Macs 是什么情況呢?
事實證明,M1 Mac 的確有一個串口,而且不需要拆機就能訪問——通過某個USB-C口!但是要想啟用串口,在必須通過 USB-PD 發送某些特殊的命令,USB-PD(USB供電)是 Type C 埠上的一種協議,使用“配置頻道(Configuration Channel)”針腳,按照 USB 標準的一貫作風,它在工程上的設計也有點過,實際能完成的作業遠不止供電——它不僅能用于配置電壓、識別充電器,還能用于識別線材、識別配接器、切換模式(如DisplayPort),在這里還被作為一個頻道,發送蘋果專屬的配置訊息,這些訊息可以讓Mac將其串口暴露在某個特定的 Type C 埠的兩個針腳上,其他的便利功能還有遠程重啟系統(對于快速開發來說是必不可少的),切換成 DFU 恢復模式,訪問 I2C 之類的內部總線,等等,
我們的第一個啟用串口的解決方案是vdmtool(https://github.com/AsahiLinux/vdmtool),這套工具包括一根使用Arduino的自制電纜,一片USB-PD PHY(介面)芯片,還有一些 1.2V 的串口配接器,雖然這些東西只需要一點 DIY 技能就可以自制,但對于沒有制作硬體經驗的人來說并不是太現實,制作程序有許多麻煩:市面上沒有能支持所有必須的Type C 信號的 USB-PD PHY 電路板,1.2V UART配接器也非常罕見,等等,
因此,我們想出了第二個解決方案:如果你正好有兩臺 M1 Macs,那就完美了!你只需要一根Type C線(SuperSpeed / USB 3.0)和 macvdmtool(https://github.com/AsahiLinux/macvdmtool),這個 macOS 上的小應用可以將一臺 M1 機器變成另一臺的串口除錯終端,這樣你就可以運行 m1n1 腳本,并從 macOS 直接引導 Linux 內核了,蘋果的 API 可以將 Mac 自己的埠配置成串口模式,還可以發送必要的訊息,將遠程 Mac 配置成串口模式,這樣不需要自定義硬體就可以實作這一切,
但是,當然將另一臺 Mac 作為串口線,這條線可夠貴的!因此,我們會以開源硬體的方式開發一種功能完整的 USB-PD 除錯線,不僅可以作為 M1 Mac 的串口配接器,還可以開放一些其他功能,實際上,這根線的用途甚至能超過Mac,作為其他設備的除錯介面,比如許多 Android 手機,它還能作為一個USB-PD 開發平臺,作為通用的供電源或負載,用于研究 USB-PD 充電器和設備,該專案還在計劃階段,但請關注后續更新!我們的最終目標是將其開放給整個社區,這樣任何人只需要點擊一個按鈕度就可以買到,
最后,盡管硬體串口是底層除錯和開發的最佳方案,但是它也有局限性:速度非常慢,最快只有 150kB/s,但是 M1 Mac 還可以作為普通的 USB 設備使用(就像 iPhone 一樣),我們可以將它作為USB串口設備(CDC-ACM),在絕大多數作業系統上,這種設備無需驅動就可以使用,這樣就能提供 USB 的全部帶寬,而且可以使用正常的 Type C 線(或Type C到Type A轉接線)連接到任何電腦,USB 還提供了流控制,因此即使接收端沒有準備好接受資料,也不至于丟失資料,這種方式的缺點是,它需要更復雜的驅動代碼,所以不適合除錯非常底層的問題,但只要能得到 m1n1 的支持,就足以進行任何后續的作業,而且我們可以使用已有的串口支持很方便地開發更復雜的驅動代碼,因為這些Mac 上的 Type C 介面可以同時傳輸 UART 串口和 USB 的信號,額外的帶寬和性能對于上面提到的監控程式的開發非常有幫助,而且還能加快加載 Linux 內核的速度,因為目前內核加載受到了串口帶寬的限制,預計接下來幾個星期內m1n1 就會支持該功能,敬請期待!

通向企鵝之路
所有這些工具都很好,但畢竟我們的目標是運行 Linux,那么,怎樣將 Linux 移植到一個全新的平臺上?當然,在整個程序中,很大一部分需要撰寫新的驅動程式,但有一些事情需要先完成,我們管這些事情叫做“鋪路”,
鋪路非常重要,不僅因為它是在機器上運行作業系統所需的其他作業的基礎,而且因為它需要為機器特有的特性的作業方式設定標準,它是緊密聯系作業系統最深處的一些底層代碼,而且與一般的驅動程式不同,它通常需要修改 Linux 中各個平臺共通的部分,這就需要與負責相應的子系統的 Linux 維護者們協調,并找出所有人都同意的解決方式,
這里面的水非常深,在最初的M1支持補丁中,我們需要更改一個與 SPARC64架構支持相關的檔案!Linux 開發的一個獨特的特性是,Linux 內核沒有穩定的驅動 API/ABI,因此 Linux 內核的內部設計一直在持續改進和重構,這就是說,如果在某個架構上支持的某個功能需要修改其他架構,那么這種修改是完全可行的,而且通常都被視為正確的做法,但這也意味著維護 Linux 的分叉或不屬于上游內核的第三方驅動變得非常困難,
Asahi Linux 的目標不僅是將 Linux 移植到蘋果芯片上,而且還要以開源社區驅動專案的形式進行,與整個 Linux 社區合作,將我們的作業推送到官方的Linux 內核中,在嵌入式 ARM 的領域中,這種方式非常罕見,因為絕大多數開發 Linux 移植版的公司都在忙于應付最終期限,所以他們會創建一個 Linux 分叉,然后在上面進行所有開發,完全脫離了上游的社區,等到他們想把修改合并到官方Linux內核中時,通常由于兩個分叉分別開發的時間過久,因此導致合并的難度非常高,其設計決策也可能與 Linux 的哲學背道而馳,從而無法被上游接受,最終,許多代碼只能重寫,只追求短期結果而忽視長期可維持性的做法導致許多開發時間白白浪費,
我們不想重蹈覆轍,所以我們的方法就是盡可能早地合并到上游,并從第一天開始就與整個社區合作,因此,我們已經與上游 Linux 維護者一起作業,而且有好幾個 Linux 的關鍵開發人員都在我們的 Asahi Linux 的 IRC 頻道中!
為了確保可以在任何系統上引導 Linux,有五項作業必須完成:
-
CPU
-
記憶體管理單元(MMU)
-
中斷控制器
-
系統時鐘
-
某種控制臺,在這里是串口控制臺
在絕大多數 AArch64 系統中,前四個非常標準:Linux 不需要任何改動就能運行到啟動基本的控制臺這一步,話雖如此,但蘋果的系統芯片就喜歡我行我素……所以我們還有許多作業要做!

關閉再打開
與八九十年代的設計相比,現代 CPU 是工程上的奇跡,過去,CPU 的作業只不過是執行簡單的算術運算、讀寫記憶體,以及做決策而已,按照順序一步步做,從不停頓,沒有電源管理,沒有快取,沒有多核心,也幾乎不支持浮點數,
但時代變了,如今的 CPU 變得越來越強大,消耗的電力也越來越少,這是怎么實作的?一部分要歸功于集成電路制造的進步,還有一部分要歸功于 CPU 設計方面的巨大進步,現在一個 CPU 的核心就能同時運行多條指令,預測未來并提前執行,如果預測錯誤就回滾,還能將經常使用的資料或預測即將使用的資料保留下來,甚至可以動態的將一部分 CPU 打開或關閉以節省電力,
但是,如此復雜的設計帶來了兩個問題:預料之外的特性,以及 bug,現在的作業系統需要更多地對 CPU 的細節進行微管理,甚至連應用程式軟體都需要注意,不要對CPU做出不實際的假設,
從九十年代就開始使用計算機的人可能還記得 Windows 95 和 Windows 98,我們無法在新的電腦上使用這些作業系統,因為CPU的溫度會迅速上升,而且會持續保持高溫,即使電腦幾乎沒有運轉也是一樣,原因就在于,這些作業系統在無所事事時也會讓 CPU 運行一個無限的回圈,因此,即使無所事事,CPU 也是 100% 處于“使用中”的 狀態!舊的 CPU 沒有“閑置”的狀態:如果沒有作業可做,就會浪費掉,沒有電源管理,所以即使無所事事也不會省電,
當然,現在我們都已經習慣了閑置的 CPU 能夠省電,作業系統在無所事事時會告訴 CPU 在某種程度上停止作業,然后等待一個事件(由外界發送的、表示需要開始作業的事件),在 x86 PC 上,這一操作由 HLT(停機)指令負責;在Windows 95 時代,曾經有一個叫做“Cpuidle”的軟體,能夠在無限回圈中運行HLT,在沒有作業時將 CPU 轉入低功耗模式,從而節約電力并降低 CPU 溫度,現代作業系統已經內置了該功能,而且 ARM 的 CPU 也實作了同樣的機制,指令名為“WFI”,意為“等待中斷”(Wait For Interrupt),
現代 CPU 在呼叫 HLT 或 WFI 時不僅會停止運行指令,還會關閉一部分核心的供電,以節省更多的電力,停止時鐘的技術叫做“clock-gating”,斷電的技術叫做“power-gating”,但是這樣做是有代價的:power-gating 會導致 CPU丟失資料,關鍵的資料必須保持在有電的電路中,或者移動到有電的備份存盤中,正常情況下,這些指令不會導致可見的資料丟失,CPU 可能會丟棄一些不再需要的資料,但會保證不丟失軟體正常作業所需的資料,
當我們幾乎在 M1 上成功引導 Linux 時,出現了一個問題:每次引導程序即將結束時就會立即崩潰,實際上,它似乎是在執行完 WFI 指令之后崩潰的:它跳轉到了一個零地址,而沒有者卻回傳到呼叫函式,為什么?
我們發現,M1 的默認運行模式中,WFI 可以做兩件事情:或者是 clock-gate,或者是 power-gate,實際上,它會根據某種啟發式的方法來決定執行哪種,不幸的是,當它決定執行 power-gate 時,CPU 就會丟失所有暫存器的內容,除了堆疊指標和指令計數器之外,Linux 并沒有預料到這件事情發生,因此,我們只能添加一個非常丑陋的補丁,因為任何其他 AArch64 的 CPU 都不會這樣做,Linux 也沒有任何機制能針對特定的系統芯片替換WFI閑置回圈,因此只能在通用的 Linux 代碼中針對特定的 CPU 進行處理,
不過,多虧了我們給 CPU 中的蘋果專有暫存器建立了檔案,并且記錄了 CPU正常作業所需的 chicken bit 序列,我們發現有一個特殊的暫存器可以用來覆寫該行為,保證WFI永遠不會執行 power-gate,從而讓 Linux 正常運行,我們只需要在 m1n1 中將該暫存器設定為正確的值,就能解決問題!這是最好的修復:m1n1 負責處理問題,因此不需要對 Linux 打補丁,
你也許想問,這樣做會不會影響系統的功耗,不要怕!這并不意味著無法使用M1 的 power-gating 功能,Linux 通過一個名為 cpuidle 的子系統支持更深層次的CPU省電模式,Linux 可以通過該子系統,將 CPU 設定成更深層的省電模式,而該子系統的驅動程式能完美地保證在 CPU 丟失資訊后能正確恢復資訊,因此,我們需要做的就是撰寫一個 cpuidle 驅動,將 M1 改回 power-gating 模式(如果 Linux 的內部演算法更好的話,也許我們可以跳過 M1 的啟發式演算法),直接在驅動程式中執行WFI,然后在回傳核心 Linux 代碼之前恢復 CPU 的資料,通過Linux的方法管理 CPU 省電,
這也展示了我們的開發程序中一個非常重要的部分,在處理沒有檔案的設備時,最簡單的方法就是保留原有軟體(macOS)的做法,但是,其他作業系統或韌體的做法也許并不適合 Linux,因此,我們要首先理解系統的優點,然后才能決定哪種方法最適合 Linux,如果我們簡單地照搬macOS的做法(在主CPU閑置回圈中支持 power gating 模式),卻沒有研究相關的 CPU 暫存器,就會給Linux 打一個非常丑陋的補丁,而錯過這種干凈的解決方法,后者的確需要更多時間,但我們認為這樣做是值得的!
這并不是 CPU 給我們帶來的唯一驚喜,不過其余的話題就以后再說吧,下面我們來討論下一個話題:記憶體管理,

投遞失敗的信退回給發信人
在這個專案剛剛開始的時候,能夠盡早獲得引導程序的反饋,對于除錯是非常重要的(我們沒有硬體除錯功能,蘋果的設備并沒有提供這些功能),前面提到的串口對于除錯非常重要,因為它只需要幾條 CPU 指令就能發送一個字符:只需要向 UART 硬體的暫存器寫入即可,Linux 有個名為 earlycon 的特性很有用,有了它,就能在主串口驅動程式啟動之前使用常見的 printk() 函式,但不幸的是,我們的第一批測驗并沒有走那么遠,因此我們只能給 Linux 中最早的ARM64 驅動代碼(用匯編撰寫的)打補丁,以便在特定的點輸出字符,來判斷哪里出了問題,
事實證明,串口只能在記憶體管理單元啟用之前使用,這很不幸,因為記憶體管理單元會改變訪問記憶體的方式,包括訪問 UART 設備的方式,但這個問題很難除錯,因為 MMU 是預先配置好,然后一次性打開的,如果里面出了問題,你很難找到問題在哪兒,
但是,經過了很長一段時間的除錯之后(最后我添加了代碼,在顯示幀緩沖區時使用不同顏色繪制出 Linux 內核的引導程序,作為另一種反饋機制),我們終于證實了 Linux 其實在繼續引導,通過了所有匯編代碼,已經開始執行 C 代碼,甚至進入了 earlycon 串口驅動,但串口沒有發回任何資料,看起來似乎它忽略了我們發給它的一切,地址是正確的,記憶體映射也是正確的,但就是沒有任何輸出,
最后發現,是由于 M1 對于設備的記憶體管理非常苛刻,
所有現代作業系統內核的核心都是記憶體管理單元,它是 CPU 的一部分,負責隔離正在運行的行程、管理虛擬記憶體(交換檔案或交換磁區)、將磁盤上的檔案映射到記憶體、在執行緒和行程之間共享資料等功能,它負責將多個虛擬記憶體地址空間(應用程式和內核擁有的記憶體地址的概念)映射到物理地址空間(系統中硬體的實際記憶體地址),在這里,“記憶體”既包括實際的 RAM,也包括作為記憶體映射 I / O(MMIO)出現的設備,UART 是 MMIO 設備,
在大多數平臺上,普通記憶體和 MMIO 之間是有區別的,我們可以認為,普通記憶體(即RAM)以某些合理的方式運行,例如在寫入資料后再讀取,則永遠會回傳寫入的資料,但是使用 MMIO 來接收命令并回傳狀態和資料的硬體卻不一樣,所以它們的行為和正常的 RAM 不一樣,CPU 可以對記憶體訪問指令進行重新排序和快取,但如果針對 MMIO 訪問進行這些操作,那就會導致一系列問題,因為驅動程式依賴精確地控制何時要發送資料、何時要接收資料,MMU 負責這個區別:內核有一個配置位元,表明記憶體是普通記憶體,還是設備記憶體,
但是,當然,如今這一切都變得復雜得多,有訪問權限問題、不同的快取模式,還有不同型別的設備記憶體,在 AArch64 上,映射設備記憶體有四種方式:GRE,nGRE,nGnRE,和nGnRnE,字母 G、RheE 代表系統被允許或不被允許(字母n表示)的三件事情:
-
G(Gather):將多個寫操作收集到一個寫操作中,例如,CPU 可以將兩個相鄰的8位寫操作合并成一個 16 位寫操作,
-
R(Re-order):對寫操作重新排序,如果依次向兩個相距很遠的地址寫入,那么CPU可能會用相反的順序寫入,
-
E(Early):提前寫入,系統可能會在資料到達目標設備之前就告訴 CPU寫入完成,從而讓 CPU 繼續執行后面的代碼,在 x86 的世界中這個操作稱為“posted write”,
絕大多數驅動和設備在 G 和 R 啟用的情況下都會出問題,所以除了非常特殊的驅動之外,很少有驅動會使用這兩個模式,但是,提前寫入(E)實際上是 PC 的標準,因為它是 PCI 規范的強制要求,因此,幾乎所有驅動都能夠處理該操作,鑒于此,AArch64 Linux 會將所有 I/O 記憶體映射成 nGnRE,同時允許提前終止,這在其他設備上沒有問題,許多設備可能并不支持 posted write,但那樣的話,它們會簡單地將訪問當作 nGnRnE 處理,設備可以提供比軟體要求更嚴格的保證,只要設備的行為與軟體要求的同樣嚴格,就不會出問題,
我們發現,M1 的內部總線結構會強制所有訪問使用 nGnRnE 模式,如果嘗試使用 nGnRE 模式,則會放棄寫操作,而系統會發出 SError(系統錯誤)信號,最初由于無意中從另一個專案引入的一個 CPU 配置,它錯誤地禁用了錯誤報告功能,我們并沒有看到這些 SError,(但即使不是因為這個錯誤的配置,由于 UART 損壞,我們也無法看到錯誤, 不過至少會讓系統在 UART 寫入后停止作業,而不是默默地丟棄它們并繼續運行),
聰明的讀者可能注意到了這里的一個有趣的細節:M1 系統芯片具有 PCIe!實際上,某些內部設備是 PCIe 設備(例如 Mac Mini 上的以太網),而且 M1 Mac 可以借助 Thunderbolt 連接到任何 PCIe 設備,難道這些設備不使用posted write 嗎?確實,它們的確會使用!實際上,M1 要求 PCI 設備必須使用 nGnRE 映射,同時會拒絕 nGnRnE 寫操作,
這帶來了一個難題,Linux 沒有將記憶體映射為 nGnRnE 的框架,我們可以引入一個臨時補丁,以便在任何地方都使用 nGnRnE(而不是 nGnRE 模式),但是那樣就不可能支持需要 nGnRE 的 PCIe 設備,于是,我們針對上游互動展開了第一項測驗:我們必須開發一種完全定制的機制,將記憶體映射為 nGnRnE,然后一種方法指示 Linux 將其用于蘋果芯片平臺上的非PCI設備,同時仍然允許PCI 驅動程式使用 nGnRE 模式,而且,我們必須以一種干凈,精心設計的方式來實作,同時還需要在不破壞現有代碼和照顧到其他非蘋果設備之間取得平衡,并與負責這些子系統的維護者達成共識,
最后,在與多個子系統和多個補丁修訂版的內核維護者進行了數周的討論之后,我們確定了如下方法:
-
引入ioremap_np(),在所有架構上,通常Linux都會使用通用的ioremap()函式映射 MMIO 設備記憶體,還有一些不十分嚴格的其他變種,例如ioremap_wt(),我們添加了一個新的變種,能特別低指定請求 non-posted 記憶體映射,
-
實作 ioremap_np() 在 ARM64 上使用 nGnRnE 模式(其他架構目前不會實作該模式,盡管這種模式對它們也許也有用,)
-
引入 nonposted-mmio 設備樹屬性,這也可以用來將設備樹中的特定總線標記為需要 ioremap_np(),
-
讓 Linux 設備樹子系統在查找設備時自動選擇 nonposted-mmio模式,并將其變成一個描述MMIO資源結構(IORESOURCE_MEM_NONPOSTED)中的一個標志,
-
撰寫兩個高層 APIdevm_ioremap_resource()和of_iomap(),自動解釋該標志,并將其“升級”成一個 ioremap_np(),
-
修改需要在 M1 系統芯片上使用的驅動程式,確保它們呼叫這些API,而不是呼叫原始的 ioremap(),
為此,我們需要對直接使用 ioremap() 的驅動程式進行一些重構,但由于只需要針對在M1上構建的硬體進行重構,所以只需要修改幾個驅動程式,如今的絕大多數 PCI 驅動都直接呼叫 ioremap(),而且所有這些都可以通過 Thunderbolt 配接器在M1電腦上使用;因此這些驅動都不需要改動,因為默認的ioremap()依然適用于仍然請求 nGnRE 模式的驅動程式,
在修改的程序中,我們意識到,Linux 缺少有關 ioremap()各種模式的檔案,也沒有關于 I/O 讀寫函式的檔案,于是,我與 Arnd Bergmann 一起添加了部分缺少的檔案(https://github.com/AsahiLinux/linux/blob/upstream-bringup-v3/Documentation/driver-api/device-io.rst#__iomem-pointer-tokens),
有趣的是,由于這部分改動針對的是通用“簡單總線”設備,因此這意味著我們必須將補丁提交給核心設備樹規范及其架構,值得慶幸的是,由于設備樹是一個開放的社區驅動專案,因此只需提交幾個 GitHub PR 即可!

這就是AIC
現代 CPU 的作業不僅是按順序運行指令,而且還要對環境的變化做出反應,這可能會要求它停止手頭的作業,轉而去做其他事情,這些通常稱為“例外”,你可能已經通過高級編程語言了解了這個概念,通常例外多用于錯誤處理,但它們在CPU 中也用于指示何時需要外部關注(類似于 POSIX 用戶空間程式中的SIGCHLD 和 SIGALRM 之類的信號),
其中最重要的就是中斷請求( interrupt request,IRQ),硬體外設通過該請求來引起 CPU 的注意,然后,CPU 運行一些作業系統代碼,確定需要關注哪些外圍設備并處理請求,
AArch64 CPU 只有一個 IRQ 輸入,這意味著需要有人收集來自系統中所有設備的中斷請求,將它們分配到正確的 CPU 核心(根據作業系統的配置),并在中斷請求觸發時告訴作業系統哪些底層設備需要關注,這就是中斷控制器(即Linux術語中的“ irqchip”)的作業,
在具有多個核心的系統上,中斷請求控制器還有另一項作業:處理處理器間中斷(inter-processor interrupt,IPI),有時,在一個核心上運行的軟體需要引起另一個核心的注意,有了IPI,這種操作就不難實作了:中斷控制器提供了一種機制,一個核心可以向中斷控制器發送請求,然后中斷控制器將其作為中斷轉發給另一個核心,沒有 IPI,多核系統將無法正常作業,
大多數AArch64系統都采用了標準的中斷控制器,稱為通用中斷控制器(Generic Interrupt Controller ,GIC),這是一個非常復雜且功能強大的中斷控制器,有許多高級特性,如中斷優先級、虛擬化等,如此一來,Linux 就不需要在AArch64系統上實作自己的 irqchips 作為主中斷控制器了,
你可能已經猜到了,蘋果依然特行獨立,他們設計了自己的蘋果中斷控制器(AIC),我們不得不對該硬體進行反向工程,然后為 Linux 撰寫自己的 irqchip 驅動程式!不過幸運的是,AIC 其實非常簡單,根據 macOS/iOS(XNU)的一些開源檔案(雖然有些過時),并通過試錯的方式對硬體進行了一番探索,我們終于弄明白了一切,并撰寫了 Linux 驅動程式,
等一下,還有一個問題,Linux 需要 IPI 才能正確作業,具體來說,Linux 使用了7種不同的 IPI:它希望能夠從一個 CPU 核心向另一個核心發送7種不同種類的中斷請求,并將它們當作不同的事件處理,AArch64 系統上的任何 IRQ 控制器都能支持這種細粒度的 IPI 分離,但不幸的是AIC不支持:它只能支持兩種,而且實際上,這兩種的使用方式還不一樣(一個用于發送給其他 CPU,一個用于核心給自己發送的“自身IPI”),為了確保 Linux 正常作業,我們需要實作一個“虛擬”中斷控制器,對于每個 CPU 核心上不同種類的待定事件,AIC 驅動程式內部最多能管理32個事件,它會將這些事件全部發送給對應于該核心的硬體IPI,當 IPI 到達該核心時,它會先檢查有哪些待定事件,然后將待定事件當作不同的IPI發送給 Linux,Linux 的其余部分就會認為這是一個能夠針對每 CPU最多處理 32 個 IPI 的中斷控制器,盡管其硬體只支持兩個(實際上我們只用到了一個),
即使是給 AIC 這樣簡單的中斷控制器撰寫驅動也不是一件易事,中斷處理有許多方面需要處理,哪怕代碼中有一點錯誤,就會引發令人苦惱的 heisenbugs,這種 bug 只在罕見的特定事件序列發生時才會出現,但一旦出現就會導致整個作業系統宕機,因此除錯幾乎是不可能的,在中斷處理程式中,輸出除錯資訊非常需要技巧,因為改變時機就可能導致 bug 消失,也可能導致整個系統過慢而無法使用,而添加一個軟體 IPI 多路復用器會導致情況更加復雜,因為我們不得不用軟體來模擬本應由硬體來處理的東西,這樣一旦出錯就會由于競爭條件而丟失 IPI,
在嘗試理解這些細節以確保 AIC 代碼正確時,我發現自己陷入了無底洞:我不得不研究 AArch64 上的記憶體順序和記憶體屏障等細節,甚至還發現了 ARM64 Linux 原子操作實作中的一個細微的錯誤!當然,這是另外一個話題,如果你想了解更多資訊,我推薦看一看Will Deacon的演講,比如這篇(https://www.youtube.com/watch?v=i6DayghhA8Q)和這篇(https://www.youtube.com/watch?v=6ORn6_35kKo),特別是,此提交(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=22ec71615d824f4f11d38d0e55a88d8956b7e45f)回答了很多問題,Will還回答了我剩余的一些疑問,我對記憶體模型和AIC代碼的健全性很有信心,這可以避免在除錯程序中感到困擾,試想一下,如果我們必須追蹤一個微妙的 GPU 掛起問題,而由于 AIC 驅動的競爭條件問題,這種問題只有在游戲中做某些事情時才會發生(但只是偶爾會發生,并且需要一個小時才能重現)!
不知是好是壞,M1 特別善長暴露這種小 bug,它的亂序執行極其強大,所以那些競合條件是在其他CPU上從來不會發生的,在除錯一個早期的 m1n1 問題時,我們甚至觀察到了亂序執行(正確地)超出了中斷處理程式的范圍……代碼才執行到了處理程式的一半,就已經輸出了除錯資訊!問題的深層原因是因為MMU 中的一個微小的錯誤配置,從這個問題你可以看出,核心系統的各個部分是緊密聯系的,而且除錯非常困難,
有意思的是,M1 芯片實際上帶有標準的 GIC,具體來說,它能夠原生地將 GIC 的底層位元虛擬化,供虛擬機系統使用!這樣就可以實作更高性能的中斷處理,因為沒有這個功能,虛擬機的監控程式就不得不模擬中斷控制器的每個細節,意味著每個中斷都需要呼叫多個監控程式中的代碼并回傳,但奇怪的是,macOS的監控程式框架(https://developer.apple.com/documentation/hypervisor/apple_silicon)并不支持該功能(至少在本文撰寫時如此),因此虛擬機的監控程式依然需要使用軟體完整模擬 GIC,我們已經測驗過這一點了,并證明了可行,現在正在與 Marc Zyngier 合作,在這些芯片上運行虛擬機;他已經成功地實作了在 M1 Mac上運行的Asahi Linux內核上運行的KVM中啟動Linux虛擬機,性能測驗還為時尚早,但我們希望,如果 macOS 不支持這個功能,那么只要其他部分完成,原生的 Linux-on-Linux 虛擬機就會比 Linux-on-macOS 虛擬機更快,特別是對于 IPI 很多的負載,

過度繁瑣的 FIQ
接下來,每個作業系統都需要一個系統時鐘,當計算機運行多個應用程式時,作業系統需要能夠在同一個 CPU 核心上切換應用程式,以實作多任務,它還需要能夠設定任務調度在特定的時間點完成,例如將快取資料寫入磁盤,或者顯示YouTube 視頻的下一幀,甚至將任務欄中的時鐘增加一秒等,所有這些都要依賴某種時鐘硬體,該硬體可以通過編程,在未來的特定時間發送 IRQ,
AArch64 包含一個特殊的系統時鐘規格,M1 也按照我們期待的方式實作了該標準,但是有一個平臺特定的位元:時鐘需要通過某個IRQ控制器發送中斷,在GIC 系統中當然是通過 GIC 發送(盡管每個系統使用的中斷編號可能不同),因此,在蘋果芯片中,就應該通過 AIC 發送,
但是,觸發時鐘中斷并要求AIC告訴我們等待的中斷的話……結果什么都得不到,什么?蘋果又一次為我們帶來了驚喜……你看,M1 的時鐘完全沒辦法發送IRQ,實際上,他們只發送 FIQ,
當我們說 AArch64 CPU 只有一個 IRQ 線的時候,我們并沒有提及它的兄弟:FIQ 線,FIQ(Fast Interrupt Request,快速中斷請求)是另一個中斷機制,這里的“快速”指的是它們比舊的 AArch32 系統作業得稍稍快一點,但在AArch64上,這點區別已經不再:FIQ 和 IRQ 實際上是相同的,在 GIC 系統中,作業系統可以配置每個中斷,決定它們通過 IRQ 還是 FIQ 發送,而絕大多數AArch64系統都保留了 FIQ 作為安全監視器(TrustZone),所以 Linux 無法使用它,因此,Linux 完全不使用 FIQ,AArch64 Linux 如果收到一個FIQ 就會宕機,它也從不會期待收到 FIQ,
沒有 FIQ 的支持,M1 上就沒有時鐘,所以別無選擇,這是為了蘋果芯片而必須做出的另一個重大修改,添加 FIQ 的支持很容易(最簡單的方式只需要機械地將IRQ的處理方式復制過來,同樣地處理 FIQ 即可),但是具體的細節很麻煩,包括決定如何為不需要的系統處理 FIQ,以及是否要在所有地方啟用 FIQ,還是在不需要的地方禁用,
最后,在思考了幾種方法,并進行了幾輪迭代之后,Linux ARM64 團隊的Mark Rutland 主動承擔了這個任務,負責給 Linux 添加 FIQ 支持,
還有另一個東西也發送 FIQ:實際上還有一個基于 FIQ 的“快速IPI”機制,我們還沒有用到,還有一個硬體性能計數器也使用它,事實上,FIQ 由每個獨立的CPU 核心或核心集群內的硬體使用,而 IRQ 由共享AIC外設(負責管理各個 CPU之間的共享硬體)使用,但是,另一個痛點就是完全沒有FIQ控制器,盡管AIC是IRQ控制器,但所有的FIQ源都“混合在一起”(ORed)形成一個 FIQ,根本無法用中心化的方式區分它們,相反,FIQ 處理代碼必須依次檢查每個 FIQ 源(檢查每個源的方式都不一樣,因為需要檢查特定的設備暫存器),找出哪個需要關注,只有需要關注時才將中斷發給該設備的驅動,這種做法非常不雅觀,我們不知道為什么蘋果不想設定一個沒有任何難度的“FIQ 控制器”,即使只設定一個暫存器,用每一位元來表示一個 FIQ 源,就足夠了,我們嘗試過尋找,甚至搜遍了每個暫存器,但似乎 FIQ 控制器并不存在,
而 M1 擁有的是一些額外的特殊功能,用于處理虛擬機作業系統的時鐘中斷(因為這是讓虛擬機正常作業的必要條件),我們也對此作了逆向工程,并將其用在了 Marc 運行 KVM 的作業中,
在針對核心 FIQ 支持的補丁之外,我們還決定將 FIQ 分發給 AIC 驅動中的下游設備驅動(即使嚴格來說它們并不是AIC的一部分),以實作這些路徑之間更緊密的耦合,如果我們決定改變通過 IRQ 發送 AIC IPI 的做法,改成通過 FIQ 發送“快速IPI”,那么這個決定將會派上用場,

歷史遺留下來的問題
能夠在設備上運行 Linux 固然很好,但如果沒辦法與之互動怎么辦?為了能訪問 dmesg 日志并通過控制臺與 Linux 互動,我們需要 M1 上的 UART 驅動程式,UART 有好幾個變種,最流行的是 PC 上的標準 UART 16550,現在幾乎所有 ARM 系統芯片都集成了這個標準,但畢竟是蘋果,他們肯定會搞自己的標準……對吧?
沒有!但是,用的不是 16550……M1 用的居然是……三星的 UART?
第一代 iPhone 采用了三星的系統芯片,即使蘋果自豪地宣布他們切換到了自己的設計,底層脫離三星的速度也要慢半拍,“蘋果芯片”與其他系統芯片一樣,包含來自許多其他公司授權的知識產權核心,例如,M1 的 USB 控制器來自Synopsys,其硬體的芯片來自 Rockchip、TI 和 NXP,甚至在蘋果將制造商從三星換成臺積電以后,一些三星的東西依然留在芯片中,UART 的設計一直保留至今,我們不知道這是否意味著M1中包含三星的知識產權,也許只不過是蘋果照搬了三星的設計來保證軟體兼容性(嚴格來說UART并不難設計),但不論如何,今天的 Exynos 芯片和蘋果芯片依然有共通點,
Linux 已經有了三星 UART 的驅動程式,但問題在于(當然會有問題):“三星UART”并非只有一個,而是有好幾個略有不同的、互不兼容的變種,而至于蘋果使用的變種,Linux 上的三星 UART 驅動并不支持,
支持許多同一硬體的變種的驅動程式會變得非常混亂,像三星 UART 這樣古老的驅動程式更是如此,更糟糕的是,Linux 中的串口子系統還是Linux早期的版本,這就帶來了另一個問題:古老的代碼,所以,最大的問題在于集成新 UART變種的支持,同時不能讓代碼變得更亂,這就意味著要做重構和清理!例如,Linux 有一個古老的概念叫做串口型別,暴露給用戶空間(意味著這些型別只能添加而不能洗掉,因為用戶空間 API 必須維持向后兼容性),但是這與現代Linux中的設備處理方式完全不同,用戶空間完全沒有理由知道串口型別是什么,即使知道,也不應該使用 TTY API 和固定的串列來訪問(這就是 sysfs 存在的原因),每個已有的三星 UART 變種都有自己的埠型別(甚至還有一個從來沒有實作過的未使用型別),但顯然我們并不想添加另一種型別……所以我們重構了驅動程式,給UART變種添加了一個內部標識,與那些暴露給用戶空間的埠型別完全無關,對于這個古老的 API 來說,蘋果的 UART 會被識別為16550,反正這個 API 也不會有人用,
另一個困難是這些變種處理中斷的方式,較老的三星 UART 有兩個獨立的中斷輸出,分別用于發送和接收,由系統中不同的中斷控制器負責,新的 Exynos 變種會在內部處理,在 UART 中有一個很小的中斷控制器,負責處理各種中斷型別,將所有中斷作為同一個發送給系統的 IRQ 控制器,蘋果的變種也是這樣,但與之并不兼容,還添加了不同的暫存器,所以必須撰寫不同的代碼路徑,
在此之上,該UART 變種僅支持邊沿觸發的中斷,邊沿觸發(edge-triggered)中斷是一種僅在事件發生時立即觸發的中斷,例如,當UART發送緩沖區清空時,與此相對的叫做狀態觸發,只要特定條件為真,狀態觸發中斷就會觸發,由于種種原因,狀態觸發中斷的處理更為簡單,所以大多數現代系統都選擇了狀態觸發,盡管 AIC 自己用的是狀態觸發中斷,而且 UART 自己的中斷也是狀態觸發,但是驅動它的內部事件(例如當傳輸或接識訓沖區為慷訓滿時)卻采用了邊沿觸發的方式!其他的三星 UART 型別支持兩種模式,而 Linux 采用了狀態觸發模式,這就導致了通過 UART 傳輸資料的 Linux 代碼造成了一個問題:現有的代碼只能打開傳輸器,然后就無所事事了,由于一切都配置為狀態傳輸模式,而傳輸緩沖區為空時會立即觸發一個中斷,而驅動程式中的中斷處理器會使用即將傳輸的資料填充緩沖區,在邊沿觸發模式下就不能這么做,因為觸發時緩沖區已經為空了,而不是即將為空,此時不會有任何事情發生,驅動程式也不會發送任何資料,我們必須讓驅動程式在資料可以發送到設備時,“立即”處理傳輸緩沖區,因為只有第一批資料發送之后才會引發中斷觸發,從而請求更多資料,
應付 UART 的這些奇怪的特性尤其麻煩,因為我們在使用 m1n1 進行試驗時,m1n1 本身就是通過 UART 控制的,嘗試研究設備的作業方式,而通信設備本身就是該設備,這就非常麻煩!不過幸好這些作業都完成了,如今 m1n1 可以正常作業了,
還有另一個驅動程式需要進行同樣的處理,不過需要使用完全不同的路線,M1 芯片中的 I2C 硬體來自 P.A.Semi!似乎 M1 中還包含一些來自 PowerPC 的遺產,而其 I2C 外設是基于 PWRficient 芯片的,包括 AmigaOne X1000 中使用的芯片,Linux 支持那個平臺,但是現有的驅動的功能非常薄弱,幸運的是,在聯系了驅動的作者之后,發現他手里依然有能正常作業的 X1000,可以幫助測驗補丁,我們還獲得了該芯片的硬體檔案,這樣我們就能改進驅動程式,并添加能夠在 X1000 上正常作業的特性(如中斷支持),同時添加支持 M1 所需的改動,由于該驅動是啟用全速 USB Type-C 埠的必要條件,所以這個作業早晚要做,

終于能見到企鵝了!
作為一部“給 Linux 鋪路”的鴻篇大論,最后我們來看一看怎樣讓 Linux 的幀緩沖控制臺在 M1 上作業,不過你可能要失望了,這個結尾并不長,
在 PC 上,UEFI 韌體會設定一個幀緩沖區,因此即使沒有合適的顯示驅動,也可以通過一個名為 efifib 的驅動來正常運行 Linux,搭載蘋果芯片 Mac 的運行方式與之相同:iBoot 會設定一個幀緩沖區供作業系統使用,我們需要做的就是使用通用的 simplefb 驅動,無需任何改動就能運行良好,我們只需在檔案中記錄一些設備樹系結方面的改動,因為雖然代碼支持,但檔案中并沒有,
于是,在所有作業之后,只需在設備樹中添加幾行,就能將黑屏變成這樣:

現在,m1n1 能夠完美地處理一切,獲取 iBoot 提供的幀緩沖區的資訊(寬度、高度、像素格式、步長和基址),并放到設備樹中,供 Linux 使用,
當然,這只是一個韌體提供的幀緩沖區,由于它并不是正常的顯示驅動,所以還不能改變解析度、處理顯示熱插拔,甚至也不能讓顯示幕休眠,對于開發和演示來說足夠了,但我們還需要撰寫一個合適的顯示控制器,
當然,還有 GPU,它并不是顯示控制器,而是一個邏輯上完全分離的硬體,PC用戶經常會混淆兩者,因為兩者放到同一個名為“顯卡”的芯片中,但在邏輯上兩者是截然不同的,在 M1 這種系統芯片上,顯示控制器和 GPU 之間的關系就像USB 控制器和 GPU 一樣,GPU 支持本身又是另一篇史詩了,所以敬請關注!
原文鏈接:https://asahilinux.org/2021/03/progress-report-january-february-2021/
宣告:本文由CSDN翻譯,轉載請說明來源,
更多精彩技術內容,請掃碼關注CSDN官方公眾號!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/272607.html
標籤:AI
下一篇:模型評估與模型選擇
