摘 要:介绍了ARM平台下UEFI的相关概念和引导流程,基于TI公司的OMAP4460Cortex A9处理器开发平台分析了ARM-UEFI各个执行阶段的移植过程。通过具体实例说明了ARM平台中UEFI设备驱动程序的移植及开发方法。
关键词:ARM-UEFI;OMAP4460;设备驱动程序
统一可扩展固件接口UEFI(Unified Extensible Firmware Interface)在设计之初被定义为一种与处理器架构无关的接口标准。UEFI接口可以采用多种不同的架构实现,对外则都表现为相同的接口。这样,UEFI就可以最大限度地保证不同设计间的代码重用,其中也包括不同处理器架构的平台[1]。2009年发布的UEFI 2.3规范与传统的BIOS和UEFI早期版本只支持X86架构的处理器相比,最大的改进在于支持不同架构的处理器平台,ARM-UEFI成为了规范的一部分。2013年发布的UEFI2.4规范中包含了对ARM 64位架构处理器的支持。这些都表明UEFI对ARM系统预引导固件的支持是一个新的机遇。使用基于UEFI标准的ARM预引导固件具有很多优越性,UEFI不仅可以实现不同架构平台之间的代码共享,还可以共享标准外设(UART、Ethernet、USB控制器等)的驱动代码以及丰富的标准函数库接口。同时,ARM-UEFI的实现为ARM系统提供了真正独立于操作系统的启动解决方案,而其他大多数现有的ARM启动解决方案(UBoot、Redboot等)都是与其所支持的操作系统相耦合[2]。本文详述了基于OMAP4460嵌入式平台的ARM-UEFI的移植过程及其外设的UEFI驱动程序的开发方法。
1 OMAP4460处理器及平台硬件结构
OMAP4460是TI公司专为智能手机、平板计算机以及具有丰富多媒体功能的移动终端设计的一款高性能、高集成度的嵌入式异构多核处理器[3]。该处理器包括2个Cortex A9内核、1个DSP内核、2个Cortex-M3内核、1个GPU以及丰富的I/O接口资源。在OMAP4460中,嵌入式操作系统运行在ARM内核上,算法的运算工作由DSP内核完成,二者通过TI提供的Codec Engine机制进行数据交互,实现了OMAP4460处理器内部多核的协同工作。
OMAP4460开发平台的硬件结构框图如图1所示。硬件平台由核心模块和扩展板两部分组成,核心模块包括OMAP4460处理器、TWL6032电源管理芯片和1 GB的DRAM;扩展板主要实现外设接口电路,包括 SD/TF卡接口、UART接口、USB接口、以太网接口和DVI/HDMI接口等。
2 ARM-UEFI引导流程
通用UEFI根据不同时间段内平台初始化所要完成的任务将操作系统启动过程按顺序分为SEC、PEI、DXE和BDS[4] 4个阶段。其中SEC(安全)阶段检测并验证固件镜像的完整性,同时建立C语言执行环境的堆栈;PEI(预EFI初始化)阶段主要完成内存初始化,将固件镜像加载至内存中执行;DXE(驱动执行环境)阶段完成平台初始化,并为操作系统引导提供软件抽象的服务;BDS(引导设备选择)阶段从引导设备中启动操作系统,将控制权交给操作系统,完成引导过程。UEFI上电执行顺序如图2所示。
目前ARM平台的启动方式多种多样,Bootloader通常采用多阶段的启动过程[5]。首阶段确定启动方式,执行必要的初始化操作,加载后续阶段镜像,后续阶段提供复杂的功能用于引导操作系统。这种方式既能够保证Bootloader的功能性,又具有很好的可移植性。因此对于OMAP4460平台的启动方案,采用x-loader + UEFI两个启动阶段的方式引导操作系统。x-loader作为第一阶段的固件,要执行时钟、内存控制器等部分硬件的初始化,并确定OMAP4460平台的启动方式,最终将UEFI加载到内存中并跳转到指定地址执行。内存初始化已在x-loader中完成,故UEFI不再实现SEC以及PEI阶段的大部分功能,OMAP4460平台ARM-UEFI的PEI阶段只需要完成系统内存映射和UEFI执行环境堆栈的建立,并执行硬件平台相关模块的初始化配置,之后UEFI跳转进入DXE和BDS阶段。本方案DXE以及BDS阶段的执行策略和实现功能与通用UEFI固件基本相同:在DXE阶段加载所有的驱动程序,完成平台初始化工作;在BDS阶段创建控制台,设置内核的启动参数,加载引导设备的内核镜像到内存中执行。OMAP4460平台ARM-UEFI引导流程图如图3所示。
3 ARM-UEFI开发与移植
3.1 移植x-loader
OMAP4460处理器内部的SRAM只有64 KB,相对于第二阶段UEFI固件镜像的数百kB而言过小,OMAP4460处理器无法加载UEFI固件镜像到SRAM运行,而需要使用外部DRAM加载。因此,本方案采用基于精简UBoot基本代码的x-loader作为第一阶段的引导固件。由于SRAM容量的限制,x-loader的初始化操作只针对必要的硬件模块,如引脚复用和功能设置、时钟、内存控制器等。初始化操作的目的是将UEFI固件镜像从OMAP446平台的SD卡中加载到DRAM中,然后跳转到入口地址执行。
x-loader的入口位于Start.s文件,Start.s首先执行CPU的基本初始化,包括禁止Cache和TLB、关闭中断等。其次执行两个跳转模块cpu_init_crit和start_armboot,cpu_init_crit模块跳转到s_init()函数执行,s_init()中调用了3个函数实现平台基本初始化:set_muxconf_regs()实现处理器芯片引脚复用和功能设置;ddr_init()实现内存初始化;prcm_init()则实现平台各个模块时钟的初始化配置。这3个函数的修改要针对OMAP4460平台的具体硬件结构以及引导过程中所使用到的模块进行。start_armboot则是在内存初始化完成且堆栈建立以后执行,程序跳转至lib\board.c文件的start_armboot()函数,实现加载UEFI固件镜像到指定内存位置的过程。第二阶段UEFI固件镜像的入口地址定义为CFG_LOADADDR,它必须与OAMP4460平台UEFI固件镜像的起始地址相同,后者由固件的fdf(Flash描述文件)指定。最终x-loader使用函数指针的方式实现x-loader到UEFI的跳转,代码如下:
((init_fnc_t *)CFG_LOADADDR)();
3.2 移植PEI阶段代码
如上所述,OMAP4460平台的ARM-UEFI没有SEC阶段,且PEI阶段实现功能与通用UEFI的PEI阶段也有所不同,PEI阶段不再需要完成内存初始化任务,而是侧重于系统平台信息、内存资源的收集过程,并通过信息描述块HOB传递给DXE阶段。OMAP4460平台的PEI阶段的主体实现代码位于ArmPlatformPkg\PrePi.c的PrePiMain()函数中,该函数主要实现了3个功能: PEI阶段的串口调试信息输出;调用PEI模块MemoryPeim实现MMU的初始化,建立UEFI系统堆栈,并将这些系统资源建立成HOB传递给后续阶段;调用PlatformPeim实现一些硬件模块的初始化和配置。
PEI阶段的串口输出是通过调用SerialPortLib中的库函数实现的。SerialPortLib主要包含两部分:串口初始化函数和串口读写函数。初始化函数SerialPortInitialize()首先配置平台UART接口的引脚复用,使能时钟;然后再设置串口通信模式,配置串口通信的波特率等参数;最后使能FIFO,完成UART接口的初始化。而读写函数则是通过轮询标志位的方式判断FIFO是否为空,再按字节读写FIFO。PEI阶段调试信息输出样例代码如下:
SerialPortInitialize (); //UART模块初始化
CharCount = AsciiSPrint (Buffer,sizeof(Buffer),"UEFI \n");
SerialPortWrite((UINT8 *) Buffer, CharCount);//数据输出
PlatformPeim中再次实现了平台模块的初始化,目的是向后兼容未来采用通用UEFI引导方式的ARM平台的PEI阶段所要实现的完整功能。PlatformPeim中包括了3个函数:PadConfiguration()、ClockInit()和GpmcInit()。PadConfiguration()函数和ClockInit()函数都通过操作宏定义的方式实现平台功能。PadConfiguration()完成平台所有引脚复用功能的设置。ClockInit()完成平台所有模块时钟的配置,并使能部分模块时钟。这种实现方式的好处是有统一的启动代码,开发DXE阶段的外设驱动程序时不需要再配置外设接口的引脚和时钟。引脚复用配置代码如下:
typedef struct {
UINTN Pin; //引脚寄存器地址
UINTN ConfigValue; //引脚属性功能
} PAD_CONFIGURATION;
MmioWrite16(PadConfigurationTableShared[Index].Pin,
PadConfigurationTableShared[Index].ConfigValue);
数据结构PAD_CONFIGURATION类型的数组PadConfigurationTableShared中存放的是OMAP4460处理器芯片所有引脚的寄存器地址宏定义和引脚属性宏定义。MmioWrite16()函数实现16位寄存器的写功能,通过获取指定引脚寄存器的地址,写入指定的配置属性,完成引脚复用功能的配置。
GpmcInit()则用于配置通用存储控制器GPMC的片选信号,样例代码如下:
for (i = 0; i < 8; i++)
MmioAnd32(GPMC_CONFIG7_0 + 0x30*i, ~BIT6);
PEI的工作完成之后,解压固件镜像的压缩部分到内存,并直接跳转至DXE阶段执行,代码如下:
Status = DecompressFirstFv (); //解压固件卷
Status = LoadDxeCoreFromFv(NULL, 0);//加载DXE核心
3.3 开发设备驱动程序
EDK II开发工具是基于X86平台的PCI总线结构实现的。当用其开发OMAP4460平台ARM-UEFI时,需要重构平台的接口控制器驱动程序,为上层提供标准的UEFI服务。同时,完整移植处于上层并满足工业标准的接口驱动程序,从而实现对原有代码的重用,加快ARM-UEFI的开发。
以显示驱动程序的开发为例,在OMAP4460平台上使用HDMI作为ARM-UEFI引导时的显示接口。ARM-UEFI系统中只需实现HDMI显示驱动程序即可实现显示控制台的输出。HDMI显示驱动程序实现图像输出协议EFI_GRAPHICS_OUTPUT_PROTOCOL,该协议提供服务接口给图像控制台驱动程序GraphicsConsoledxe, 再由该驱动程序实现简单文本输出协议EFI_SIMPLE_TEXTOUT_
PROTOCOL,最后由虚拟控制台驱动程序Consoleplitterdxe将所有挂载简单文本输出协议的设备句柄统一挂载到虚拟控制台输出句柄ConOut上,由ConOut实现控制台的输出,OMAP4460平台UEFI图形控制台结构图如图4所示。
HDMI显示驱动程序包括三部分:显示模块硬件初始化、显示模式的配置以及图像的显示。其中GOP协议的函数接口实现显示模式配置与图像的显示,QueryMode、SetMode两个接口实现分辨率、颜色及频率的配置,Blt接口实现图像显示。GOP协议数据结构定义如下:
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE
QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
//设置显示模式
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt; //图像显示
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;//结构体
}
显示模块初始化函数InitializeDisplay()在驱动程序的入口函数中执行,具体初始化过程分为3步:
(1)使能ESD保护芯片TPD12S016的供电及热插拔引脚功能,检测HDMI接口的HDMI_HPD引脚状态,检测是否接入显示器。
(2)使能显示子系统的功能时钟和接口时钟,同时使能HDMI物理层的时钟。
(3)申请图像管道内存,并记录基地址。
HDMI接口显示模式的配置分为显示子系统和HDMI物理层两部分的显示模式配置,由QueryMode和SetMode接口函数实现。QueryMode通过HDMI的I2C接口读取显示器的扩展显示标识数据EDID(Extended Display Identification Data),EDID中包含了监视器时序、定时和分辨率等性能参数,使用这些参数填充Mode结构体,再将Mode作为改变值参数传递给SetMode接口,由SetMode完成显示子系统和HDMI物理层中显示输出时钟频率、图像分辨率、位宽等寄存器变化值的写入和重新使能,从而更新显示分辨率、图像格式以及颜色模式等参数。写寄存器样例代码如下:
MmioWrite32(HDMI_WP_VIDEO_SIZE,
((LcdModes[ModeNumber].HorizontalResolution)|
((LcdModes[ModeNumber].VerticalResolution)<<16)));
其中宏定义HDMI_WP_VIDEO_SIZE是HDMI模块定义显示器分辨率的32位寄存器地址,该寄存器低16位为水平像素,高16位为垂直像素。结构体LcdModes的HorizontalResolution和VerticalResolution参数代表显示模式需要配置的分辨率参数,通过MmioWrite32()函数将值写入寄存器,完成配置。
图像的显示由GOP协议的Blt接口函数实现,Blt函数首先调用LcdPlatformGetBpp()函数获取平台设置的图像格式以及位宽,再计算原图像像素和目标管道内存的地址,然后将EFI图像像素转换成显示器设置的像素格式,宏定义LCD_BITS_PER_PIXEL_24表示图像格式为xRGB24,即每个像素占用32位,低24位按照蓝绿红各8位排列,最高8位为空。最后将转换完成的像素数据存放在计算好的目标管道内存中,使用图像管道将图像数据输出。具体代码如下:
LcdPlatformGetBpp (This->Mode->Mode,&BitsPerPixel);
if(BitsPerPixel== LCD_BITS_PER_PIXEL_24)
{
// 计算原地址和目标地址
EfiSourcePixel = BltBuffer + SourceLine * BltBufferHori-
zontalResolution + SourcePixelX;
DestinationPixel32bit = (UINT32 *)FrameBufferBase +
DestinationLine * HorizontalResolution + DestinationPixelX;
//将原像素转换后复制到目标地址中
*DestinationPixel32bit = (UINT32) ((EfiSourcePixel->
Red<<16)|(EfiSourcePixel->Green<<8)|
(EfiSourcePixel->Blue<<0) );
}
3.4 优化BDS阶段
UEFI在BDS阶段会连接设备驱动程序、创建控制台并轮询引导设备,操作过程非常耗时。而对于嵌入式系统平台,系统引导时间有严格要求,需要优化ARM-UEFI的BDS阶段,以缩短引导时间。方案采用的策略是使用平台配置数据库PCD(Platform Configuration Database)在平台包的dsc文件(平台描述文件)中定义控制台和引导设备的路径,并在BDS阶段中分两阶段执行:首先读取控制台设备路径,连接控制台设备驱动程序;然后读取引导设备路径,连接指定引导设备的驱动程序,再从引导设备中加载内核,并将控制权移交给操作系统。至此,ARM-UEFI开发流程基本完成。
ARM-UEFI在系统耦合、代码重用方面具有明显的优势,加速系统开发的同时,还能减少开发成本。随着越来越多的厂商参与到ARM-UEFI标准的制定和实现中来,ARM-UEFI将会成为ARM系统标准的启动解决方案。
参考文献
[1] 石浚菁.EFI接口 BIOS 驱动体系的设计、实现与应用[D]. 南京:南京航空航天大学,2006.
[2] 魏东.UEFI-A new opportunity for preboot firmware on ARM-based system[Z].2013.
[3] TI.OMAP4460 multimedia device silicon revision:technical reference manual[Z].2012.
[4] ZIMMER V,ROTHMAN M,MARISETTY S.Beyond BIOS:developing with the unified extensible firmware interface (2 Edition)[M].Intel Press,2010.
[5] 宋宝华.Linux设备驱动开发详解(第二版)[M].北京:人民邮电出版社,2010.