基于ARMv8的固件系统体系结构
基于ARMv8的固件系统体系结构
The architecture of ARMv8-based firmware systems
自2011年发布以来,ARMv8处理器架构在移动设备市场上已经相当普及。根据ARM有限公司首席执行官的预测,到2020年,这一代处理器将获得高达25%的世界市场份额。通过继承历史上形成的基础设施的特性和一般原则,软件支持得以建立并得到进一步发展,这是很自然的。
在服务器细分市场上,观察到了一种根本不同的情况。基于X86的服务器在这一领域占据主导地位已有很长一段时间,而ARMv8只是找到了自己的路(而且只进入了特定的业务领域)。ARM市场的新颖性以及大多数公认的标准和规范(主要是ACPI和UEFI)直到最近才适用于ARM系统,这一点在软件基础设施的开发上留下了印记。
本文着重于对基于ARM的服务器系统和处理器特性的概述,并不声称是详尽的描述。作者还想提请读者注意这样一个事实:所提供的数据可能很快就会过时——很快,新的处理器将带来新的技术解决方案,可能需要采用不同的方法来实现软件基础设施。
首先,我们应该指出,当前ARMv8服务器系统的固件实现由几个相对独立的组件组成。这提供了许多优点,例如在服务器和嵌入式系统固件中使用相同组件的可能性,以及引入的更改的相对独立性。
那么,这些系统使用了哪些模块和组件,它们的功能是什么?模块的加载和交互的总体图如图1所示。这个过程从初始化子系统开始,比如RAM和处理器间接口。在当前的实现中,这是由EL3S模式下的一个单独的模块在打开主CPU电源后立即执行的。因此,系统的这个组件拥有最大的权限。它通常不与操作系统直接交互。
图1. 模块的加载和交互。
之后,控制权被转移到下一个组件,通常是ARM可信固件(ATF)模块,该模块在相同的模式下执行。ATF控制可以直接从上一段描述的0级加载器传输,也可以通过实现PEI(PreEFI初始化)的特殊UEFI模块间接传输。ATF由几个在不同时间接收控制的模块组成。
BL1启动模块执行分配给安全处理器模式的平台部件的初始化。由于基于ARMv8的系统对可信和非可信资源(包括RAM)使用硬件分离,BL1模块为可信代码的执行准备了一个环境。具体而言,这种类型的初始化包括存储器/高速缓存控制器的配置(通过对这些设备中的寄存器的编程来标记可信和非可信区域)和片上设备的标记(能量无关的内存控制器)。这个标记还引入了基于设备类型(可信/不可信)的DMA事务过滤。考虑到所有这些,内存的写入/读取只能从安全设置与设备的安全设置相匹配的区域进行。可信环境的实现可能相当复杂;例如,它们可以包含一个单独的操作系统。但是,这种实现的描述超出了本文的范围。
BL1模块配置MMU地址转换表和异常处理程序表,其中最重要的元素是安全监视器调用(SMC)指令的异常处理程序。在这一点上,处理程序是最小的,实际上只能将控制权转移到加载到RAM中的映像。运行时,BL1模块将下一级(BL2)加载到RAM中,并将控制权转移给它。BL2模块在EL1S模式下工作,权限减少。因此,使用“ERET”指令执行向该模块的控制转移。
BL2模块的目的是加载剩余的固件模块(BL3部件)并将控制权转移给它们。降低的特权级别用于避免可能损坏内存中已有的代码和EL3S数据。这些部件的代码是通过使用SMC指令调用位于BL1阶段的EL3S代码来执行的。
ATF加载和初始化的第三阶段可包括三个阶段,但第二阶段通常被省略。因此,事实上,只剩下两个。BL3-1模块是可信代码的一部分,通用软件(OS等)可以在运行时访问它。这个模块的关键部分是由“SMC”指令调用的异常处理程序。模块本身有实现标准SMC调用的功能:实现标准PSCI接口(设计用于控制整个平台,例如启用/禁用处理器核心、平台范围内的电源管理和重新启动)以及处理特定于供应商的调用(提供有关平台的信息,管理嵌入式设备等)。
如上所述,BL3-2模块的存在是可选的;其代码(对于模块而言)是在EL1S模式下执行的。通常,它作为平台操作期间发生的事件(来自某些计时器、设备等的中断)的专用服务/监视器
实际上,BL3-3不是ATF模块,而是在非安全模式下执行的固件映像。它通常在EL2模式下获得控制权,并表示类似于众所周知的U-Boot的引导加载程序的映像,或者表示UEFI环境的映像,UEFI环境是服务器系统的标准配置。
ATF模块初始化的总体图如图2所示。
图2. ATF模块初始化。
在某些基于ARMv8的服务器系统中可以使用另一个初始化路径:ATF在UEFI PEI阶段启动,之后转换到UEFI DXE阶段。
armv8uefi与x86上的有很大不同。PEI和DXE(驱动程序)阶段同时用于x86和ARMv8。然而,在许多ARMv8系统上,PEI阶段显著减少,并且在此期间不执行硬件初始化。该阶段包括设置MMU转换表、配置中断控制器和CPU定时器(根据UEFI规范,此环境中唯一处理的中断是定时器中断)、构建EFI切换块(HOB)和执行DXE内核。在这个阶段,本地UEFI模块倾向于使用上面描述的特定于平台的SMC调用。
UEFI的大部分工作在DXE阶段执行。首先,这涉及到加载和启动硬件驱动程序,包括片上外围设备和通过PCIe、USB、SATA等接口连接的外部设备。
应该注意的是,基于ARMv8的系统在配置、设备检测机制等方面与基于x86体系结构的类似系统有很大的不同。例如,x86的主要设备检测机制是扫描PCI配置空间并为设备分配内存地址,这些设备必须对其进行解码。在基于ARMv8的系统中,内置外设几乎总是在内存空间中有固定地址(端口是未使用的,因为它们不受CPU体系结构的支持),并且在某些情况下在PCI配置空间中不可见。对于这样的系统,有一个由平坦的设备树组成的硬件描述,设备连接的树状描述也描述了诸如与这些设备相关联的存储器范围和中断号等资源。
在更高级的系统中,soc支持通过PCI配置空间进行访问,并包含通过增强配置访问机制(ECAM)实现对该空间的访问的控制器。由于这些单元的内存地址是固定的,因此通用的PCI设备配置机制不适用。具体而言,对于具有固定PCI设备地址窗口的系统,开发了增强的分配PCI功能,解决了这一矛盾。可以单独写一篇文章来介绍这种功能的独特属性。简言之,它可以描述为一组替代寄存器,其中包含有关内存地址、总线号(对于内置PCI-PCI桥)等的信息。
UEFI与另一种传递平台配置信息的方法ACPI是分不开的。目前,为改进对ARMv8体系结构的支持而开发和完善ACPI规范的工作正在进行中。根据现有信息,ACPI应该成为描述ARMv8平台及其管理的基本信息(主要是处理器内核、PCI/PCIe控制器的数量和配置)及其管理的关键方法。一些计划发布的ARMv8操作系统只支持ACPI机制。
DXE阶段包括设备检测和它们在UEFI中的初始化和注册,以及操作系统启动的准备。后者包括准备系统内存映射和配置信息,即加载、生成和发布ACPI表,修改这些表以反映平台的当前配置,对FDT进行类似的更改,以及检查和生成校验和。在此阶段加载的模块可以实现UEFI运行时服务(UEFI Runtime Services),这些功能可在运行时从操作系统调用。值得注意的是,在本文作者使用过的所有系统中,设备检测都是通过PCI-ECAM机制实现的。
完成此阶段后,启动设备选择(BDS)开始。在这个阶段通常使用一个单独的模块来处理“BootOrder”、“BootNext”和其他相关变量的值。通常,此模块实现(伪)图形用户界面。此时,基于x86的系统有许多共同点:使用相同的引导方法–PXE、iSCSI、块设备(如SATA/SAS/USB驱动器、SSD和NVME设备)等。
有必要提醒读者注意ARMv8 UEFI的外部设备(通常是PCIe设备)的驱动程序。它们可以以模块的形式实现,这些模块位于存储设备(FAT32文件系统)中,也可以直接驻留在设备中(可选ROM)。在某些情况下,将ARMv8添加到支持的体系结构列表中会给供应商带来问题。简单地重新编译ARMv8的源代码并不总是足够的,因为有些模块并没有设计成在完整的64位地址空间中工作。由于在ARMv8系统中广泛使用PCI总线到处理器地址的转换,反之亦然,也可能会出现困难。这是由于决定放弃位于内存地址空间低32位的旧“窗口”造成的。在支持增强方面,用EBC字节码编译的驱动程序可以提供所需的兼容性级别。然而,在撰写本文时,ARMv8的EBC解释器处于开发的早期阶段。
控制权转移到加载到内存中的模块(引导加载程序或直接进入操作系统内核)是根据UEFI规范执行的:模块的UEFI句柄在X0寄存器中,系统表指针在X1中,返回地址在X30(LR)中。
操作系统内核使用UEFI服务执行一些准备步骤,然后设置自己的转换表并调用UEFI方法ExitBootServices()和SetVirtualAddressMap()。这是必要的,因为UEFI代码在与操作系统内核相同的地址空间中执行。此外,定时器中断和任何可能的DMA传输都必须被禁用。armv8linux操作系统设计有一个值得注意的方面:主内核代码在EL1模式下执行,而EL2模式只保留给KVM管理程序代码的一部分。因此,在初始化期间,内核将其特权级别从EL2降到EL1。之后,只有运行时服务(所有UEFI服务的子集)可用于内核。如前所述,ARMv8上的Linux内核在ATF模块中实现时广泛使用PSCI接口。这尤其是多核系统的特点。接口本身和二级CPU核心初始化过程可以简单地描述为以PSCI函数号和初始化函数的入口点为参数发出SMC调用。事实上,对UEFI和SMC服务的调用是当前操作系统和固件之间交互的主要方式。有其他固件事件通知设施的规范草案,但到目前为止(2015年)还没有任何完成实施的报告。
总而言之,应该提到的是,本文没有详尽地描述基于ARMv8的固件组件的功能和交互。