原创翻译,转载请注明出处。算法
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)