原創翻譯,轉載請註明出處。算法
arm64的異常模型由一組異常級別(EL0-EL3)組成。EL0,EL1有安全模式和非安全模式的區別。EL2是虛擬機管理級別而且只有非安全模式。EL3是最高優先級而且只存在安全模式中。
爲了描述方便,下面將使用術語「boot loader」來簡化全部執行在cpu將控制權轉交給內核以前的軟件的稱呼。這裏包含了安全監視器(secure monitor)和虛擬機管理器(hypervisor)的代碼,或者多是少許用來準備一個最小的啓動環境的指令。
基本上,boot loader至少提供如下幾個功能:編程
一、安裝與初始化物理內存
boot loader須要初始化物理內存,內核將使用這些內存來存儲volatile類型的數據。這個是與機器有關的,可能使用了內部算法自動的定位並取得物理內存的大小,緩存
或者多是機器有關內存方面的特性,也多是boot loader設計者知道的獲取內存某種方法。(囧)
二、安裝設備樹
dtb(device tree blob)必須位於8-BYTE對齊的位置而且不能超過2MB的大小。由於dtb會被映射到最大2MB的緩存塊上,它不能放在任何映射了特定屬性的2M區域內。
注意,在內核4.2之前,要求將DTB放在內核鏡像裏以text_offset爲起始位置的512M區域內。
三、解壓內核鏡像(這個是可選的)
arm64(aarch64)的內核當前並不提供自解壓功能,所以須要解壓在boot loader裏完成(好比gzip格式)。若是boot loader不支持解壓,可使用不壓縮的鏡像來啓動。
四、啓動內核鏡像
解壓後的內核鏡像包含64byte的頭,頭結構定義以下:安全
u32 code0; /* Executable code */ u32 code1; /* Executable code */ u64 text_offset; /* Image load offset, little endian */ u64 image_size; /* Effective Image size, little endian */ u64 flags; /* kernel flags, little endian */ u64 res2 = 0; /* reserved */ u64 res3 = 0; /* reserved */ u64 res4 = 0; /* reserved */ u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */ u32 res5; /* reserved (used for PE COFF offset) */
頭結構說明:
(1)、在內核3.17版本之前,全部字段都是小端字節序,除非有特別說明。
(2)、code0/code1 是爲了響應 stext 分支。
(3)、若是以EFI(可擴展固件接口 Extensible Firmware Interface)啓動,code0/code1一開始就會被跳過。res5是指PE頭和有EFI入口點(efi_stub_entry)的PE頭的偏移。當efi完成了它的工做,就會跳轉到 code0 的位置繼續正常的啓動流程。
(4)、在內核3.17版本之前,text_offset字段的字節序是不肯定的。舉個栗子,在內核字節序裏,image_size 爲0,text_offset爲 0x80000。若是 image_size 是一個非0值,必須注意了,這時image_size 是小端字節序。若是 image_size 爲0,那麼text_offset能夠認爲是 0x80000。
(5)、flags 字段(內核3.17版本引入的)是一個小端字節序的64bit字段,它的組成以下:bash
Bit 0: 內核字節序標識, 1是大端, 0是小端; Bit 1-2:內核頁的大小, 0 - 表示未說明,1 - 表示4K大小,2 - 16K,3 - 64K; Bit 3:內核物理佈局, 0 - 基地址2MB對齊而且基地址應該離DRAM的基地址越可能的近,由於是線性映射,因此內存地址低於它的不能訪問。 1 - 基地址2MB對齊,能夠位於物理內存的任何地方。 Bit 4-63:保留字段。
(6)若是 image_size 爲0,在內核鏡像啓動結束以後,bootloader應提供儘可能多的空閒內存給內核使用。這個空間的數量會隨着不一樣的特性變化,其實是沒有明確的限制的。
內核鏡像位於任何可用的 text_offset 大小的字節數的內存基地址上,這個地址必須是2MB對齊的。2MB對齊的基地址與內核鏡像起始位置這之間的區域對內核是沒有特別的意義的,能夠用做它用。網絡
在內核鏡像起始的位置起,至少 image_size 大小的字節數必須是空閒的,以供內核使用。
注意:在內核4.6版本以前不能使用低於鏡像大小的物理偏移的內存,因此推薦鏡像放在離物理內存地址起始位置儘量近的地方。
若是 initd/initramfs 在啓動的時候傳遞給了內核,它必須整個屬於1GB對齊的物理內存窗口到32GB大小之間,以所有覆蓋內核鏡像爲好。
任何描述給內核的內存(包括低於鏡像起始地址的),若是沒有標記爲保留的(dtb裏的 /memreserve指定)將被內核認爲是可使用的。
在跳轉到內核以前,下面的條件必須知足:
(1)禁用全部的具備DMA能力的設備,這樣內存就不會被僞造的網絡數據包或者硬盤數據污染。這將節省你大量的調試時間。
(2)主CPU的通用寄存器設置:oop
x0 = dtb在系統內存的物理地址 x1 = 0 (保留給之後使用) x2 = 0 (保留給之後使用) x3 = 0 (保留給之後使用)
(3)CPU模式
全部的中斷都必須在 PSTATE.DAIF (Debug,SError,IRQ,FIQ) 中設置掩碼位。CPU必須處於EL2(推薦模式,方便虛擬化擴展訪問)或者非安全模式的EL1模式中。
(4)Caches,MMUs
MMU必須關閉。
指令緩存能夠開啓或關閉。
對應於內核鏡像的地址範圍應該清理成PoC(PoC不知道是啥)。要使能系統緩存或者其餘一致性主緩存,要求緩存維護經過VA,而不是 set/way 操做。
系統緩存 (依賴體系結構的緩存維護經過VA操做的)必須被配置而且使能,而不依賴體系結構經過VA維護的系統緩存必須禁止。
(5)定時器
CNTFRQ 必須對定時器頻率是可編程的,而且CNTVOFF必須對在全部CPU上具備一致性的值是可編程的。若是進入內核時是在EL1模式,CNTHCTL_EL2 必須有EL1PCTEN (bit 0)設置可用。
(6)一致性
全部CPU經過內核啓動必須是相同一致的內核入口的一部分。這將要求「IMPLEMENTATION DEFINED」的初始化來使能每一個CPU來接收維護操做。
(7)系統寄存器
全部可寫的系統寄存器在這內核鏡像將要進入的異常級別(EL)必須在一個更高的異常級別(EL)經過軟件初始化,來防止在一個未知的狀態執行。
在一個有GICv3的中斷控制器的系統可使用v3模式:佈局
一、若是是 EL3 : ICC_SRE_EL3.Enable (bit 3) 必須初始化爲 0b1. ICC_SRE_EL3.SRE (bit 0) 必須初始化爲 0b1. 二、內核是在 EL1 : ICC.SRE_EL2.Enable (bit 3) 必須初始化爲 0b1 ICC_SRE_EL2.SRE (bit 0) 必須初始化爲 0b1. 三、DT或者ACPI表必須在GICv3中斷控制器中。
上述的CPU模式,緩存,MMU,定時器,一致性,系統寄存器對應全部的CPU,全部CPU必須在相同異常級別進入內核。
bootloader在進入內核(每一個cpu)都有以下規則:
(1)主CPU直接跳轉到內核鏡像的第一條指令。dtb傳給每一個CPU必須包含「enable-method」屬性,這個屬性在下面會描述。
bootloader會生成這些設備樹的屬性並在內核入口以前插入到二進制執行文件中。
(2)帶有「spin-table」使能方法的CPU必須有一個「cpu-release-addr」的屬性節點。這個屬性標識符以64bit天然對齊並在內存中初始化爲0。
這些CPU在內核以外的保留的內存區域(dtb裏的 /memreserve/ 的指定區域)空轉,並輪詢「cpu-release-addr」地址,該地址也在保留區域內。es5
「wfe」指令能夠用來插入減小這種busy-loop的開銷,而且主CPU會發出「sev」(嘛東西。)。 當讀取「cpu-release-addr」返回一個非0值,這個CPU必須跳轉到這個值的地址。翻譯
這個值就是一個簡單的64bit的小端的數值,全部這些cpu必須轉換成它本身的原生字節序以後才能跳轉過去。
(3)具備「psci」的使能方法的CPU應該停留在內核以外的保留內存區域。內核會發出「CPU_ON」的調用來將CPU帶入內核。
設備樹應該包含一個「psci」節點。能夠參考Documentation/devicetree/bindings/arm/psci.txt。
(4)從CPU上的通用寄存器設置:
x0 = 0 (reserved for future use) x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use)