DM368开发 -- Bootloader 开发(转毕设)

来源:互联网 发布:淘宝卖家如何做好运营 编辑:程序博客网 时间:2024/06/16 03:12

参看:基于 DM368 的高清视频监控系统设计与实现 -- 文波

DM368 的启动过程大致如下: 首先进行启动代码的加载与执行, 主要是指 Bootloader启动阶段,其次启动系统内核并挂载文件系统,最后运行应用程序。其过程如图 4.1 所示。本系统中 Bootloader 分为 3 个阶段:RBL(Rom Boot Loader),UBL(Use Boot Loader)和 U-boot(Universal Bootloader)。


一、 Bootloader 简介

Bootloader 是嵌入式系统的引导加载程序,主要存放在 DM368 目标板的非易失性存储器中(通常为 FLASH 存储器) ,作用类似于 PC 机上的 BIOS[50]。它是系统上电后最先运行的第一段程序,主要用于对硬件设备进行初始化并建立内存空间映射,为 Linux操作系统内核的运行准备合适的软硬件运行环境[51]。在完成对系统的初始化任务后,它会将存储器中的 Linux 内核拷贝到 RAM 中, 然后跳转到内核的第一条指令处继续执行,从而启动 Linux 内核。由此可见,Bootloader 和 Linux 内核之间有着密不可分的联系,对于嵌入式操作系统来说发挥着很重要的作用。Bootloader 虽然具有初始化系统和执行用户输入的命令的作用,但它最根本的功能就是为了启动 Linux 内核。Bootloader 的操作模式分为自启动模式和交互模式。自启动模式是指操作系统的内核和文件系统已烧写到目标板的某个固态存储器芯片上,Bootloader 从目标板上的固态存储设备上将操作系统加载到 RAM 中运行,整个过程自行完成,不需要用户介入,自启动模式主要用于产品开发完成后[52]。交互模式主要应用在系统的开发调试阶段,通过串口和以太网将内核映像和文件系统从主机上下载到目标板的内存中。系统调试完成后,可以通过固定的命令将这些文件烧写到存储器芯片中,实现系统自启动。

二、系统启动方式

DM368 有两种启动模式, 第一种是从外部的异步存储器 AEMIF (NOR /One FLASH)启动,无需引导程序引导; 第二种是 ARM内部的 ROM自带 RBL引导程序引导 bootloader方式启动,首先启动 RBL,其次启动 UBL,最后启动 U-boot。DM368 中 ARM 内部的ROM 大小为 16KB,在出厂时已烧写好一段固件代码,称为 RBL(Rom Boot Loader) 。此方式支持 7 种启动方式分别为:NAND 启动方式、MMC/SD 启动方式、UART 启动方式、USB 启动方式、SPI 启动方式、EMAC 启动方式和 HPI 启动方式。DM368 的启动方式示意图如下图 4.2 所示:


当 DM368 上电后,芯片会检测 BOOT 模式配置引脚 BTSEL[2:0]的状态,根据其具体值来决定采取哪种启动方式。 当 BTSEL[2:0]=001 时, 系统采用第一种启动方式, ARM处理器从 AFMIF 执行引导启动程序,即从外部存储器 One Nand 或 Nor Flash 启动。通过 AEMIF 接口从 Nor/One NAND 里读取已经固化好的代码,起始地址从 0x02000000开始。同时通过寄存器 AECFG[2:0]对 Nor/One NAND 的地址线位数进行配置当BTSEL[2:0]≠001 时, 系统采用第二种启动方式, 即 RBL 引导 bootloader 方式启动模式。ARM 处理器从其内部 ROM 地址 0x00008000 处开始执行 RBL 程序,之后 RBL 根据BTSEL[2:0]的值决定启动方式。Boot 引脚值对应的启动模式如表 4.1 所示。


硬件系统选用 NAND FLASH 作为外部存储器, BTSEL[2:0]设定为 000,AECFG[2:0]设定为 000,所以系统采用 RBL 引导的 8 位 NAND FLASH 启动方式。此过程需要三个阶段,分别为 RBL、UBL 和 U-boot[53],启动过程的流程图如图 4.3 所示。


根据以上分析,DM368 的启动需要 3 级引导,即 RBL→UBL→U-boot。之所以使用UBL,是因为 DM368 的内部 RAM 空间只有 32KB,而一般的 U-boot 要大于 32KB[54]。

三、RBL 启动过程分析

RBL 是 TI 公司在芯片出厂时固化在 ARM 内部 ROM 中的一段启动程序, 起始地址为 0x00008000,终止地址为 0x0000BFFF。启动开始后,系统会从该地址读取数据,运行 RBL 程序。 RBL 从 NAND FLASH 中读取 UBL 描述符, 随后复制 UBL 到内部 RAM,并跳转到 UBL 的入口地址 0x00000020 运行 UBL。RBL 工作流程如下图 4.4 所示。


启动开始后,RBL 首先通过 EM_CE0 和 AECFG[2:0]搜索到 NAND FLASH 设备,并获取到 FLASH 的相关信息,如设备的 ID。通过此 ID 可以获得 NAND FLASH 相对应的信息。 其次 RBL 开始在 NAND FLASH 中搜索有效的 UBL 描述符, 从 NAND FLASH中第 1 块的第 0 页开始依次搜索,至到最后一块的最后一页为止。如果全部搜索完毕没有发现有效的 UBL 描述符,则系统尝试转入 MMC/SD 卡启动方式启动。如果 RBL搜索到合法的 UBL,则对 UBL 的描述符采取读入和执行措施,同时将有效的块号写入到 ARM 内部 RAM 最后的 32 位地址中,从 0x00007FFC 到 0x00008000[55]。UBL 描述符包含有 UBL 特殊信息描述符 MagicNumber、入口地址、页数、开始块数和结束块等基本信息。当 RBL 找到 UBL 描述符信息后,会读取并处理该信息,并完成 UBL 启动配置, 之后 RBL 会把 UBL 从 NAND FLASH 中拷贝到 ARM 内部的 RAM 中, 执行 UBL的地址从 0x00000020 开始。在 RBL 对 UBL 拷贝过程过程中,RBL 对 UBL 进行 4 位的ECC 错误校验和检测机制,如果 RBL 检测出拷贝的数据出错,UBL 会通过 ECC 检验算法纠整错误。如果纠错失败,RBL 放弃当前块,继续从下一块开始搜索有效地 UBL描述符。最后当 RBL 完成 UBL 的搬运工作后,RBL 会将系统指针指向 UBL 起始地址,并将控制权交给 UBL,UBL 程序接着开始运行。

四、UBL 启动过程分析

UBL 主要功能是实现将 U-boot 代码拷贝到 DDR2 内存中,建立运行环境并引导U-boot。在嵌入式操作系统中,U-boot 的大小一般都较大,基本上都在 100KB 以上。本系统中 DM368 中 ARM 核内置的 RAM 大小仅为 32KB,RBL 无法直接将 U-boot 代码拷贝到 RAM 中,只能加载到 DDR2 外部存储器中,用 UBL 引导 U-boot 的方式进行启动。UBL 存放在 DVSDK_DM368_4_02_00_06/psp/flash-utils/DM36X 和flash-uyils/common 目录下。common 目录里存放通用程序信息,包括 UBL 的驱动源码、工具、脚本等,DM36x 存放与平台相关的程序信息。UBL 的工作流程如下图 4.5 所示。


转到 ubl.c 中的 main 函数, main 函数主要调用 LOCAL_boot 函数进行实质的引导。 UBL对系统的外围硬件进行初始化设置并且判断系统的启动方式, 进而完成 U-boot 向 DDR2的拷贝工作。UBL 工作完成并退出,DDR2 中的 U-boot 接管系统的管理权开始工作运行。ubl.c 中的 main 函数里面有三个重要的函数涉及到系统启动方式、硬件初始化和U-boot 拷贝,对应的函数分别为 DEVICE_bootMode()、DVICE_init()和 nandboot_copy。
以下对三个主要函数的功能做出分析:
DEVICE_bootMode 函数通过读取寄存器的值可获取系统的启动信息。UBL 会判断系统的启动方式,UBL 程序支持 NAND FLASH、NOR FLASH、MMC/SD 和 UART 启动方式。在 4.2.2 小节中,系统采用 NAND 启动方式。
DEVICE_init 函数对系统设备的最底层硬件平台进行初始化,包括中断的屏蔽和清除、电源时钟使能、复用引脚功能选择、时钟频率设定、DDR2 控制器相关参数设定、EMIF 初始化、串口初始化、I2C 初始化等。NANDBOOT_copy 函数采用 NAND 启动方式,搜索 U-boot 描述符,通过此描述符配置 U-boot。UBL 完成 U-boot 从 NAND 到 DDR 内存的拷贝工作。

1.硬件设备初始化

对硬件平台最底层设备进行初始化是 UBL 工作中最重要的一部分,DEVICE_init 函数对系统硬件的初始化流程如下图 4.6 所示。根据 DM368硬件外围设备的不同, DEVICE_init函数对硬件做出相应的修改和配置。包括系统屏蔽清除所有中断、初始化电源时钟使能 PSC、管脚复用寄存器 PINMUX 的配置、系统 IO 设置、系统时钟 PLL 的配置以及对 DDR2、外部存储器、串口、定时器以及 I2C 总线的初始化。


在系统对底层硬件的初始化中,屏蔽清除所有中断、电源时钟使能、系统时钟 PLL、串口以及 I2C 总线初始化在系统引导程序中设置基本一致,故在此不做修改。以下对管脚复用和 I/O 口的配置、DDR2、EMIF、定时器初始化做出详细设计。
(1)管脚复用寄存器配置如下表 4.2 所示。



(2)外部存储器(NAND FLASH)的初始化配置如下表 4.3 所示。


(3)定时器(TIMERO)的初始化配置如下表 4.4 所示


(4)DDR2 初始化

UBL 在对底层硬件初始化的过程中,对 DDR2 的初始化是最为重要的部分。U-boot在由 UBL 拷贝到 DDR2 内存之前必须对 DDR2 进行初始化工作并对其进行相应的配置,这是因为在系统上电后, ARM 内核中的 ROM 固化程序 RBL 没有对 DDR2 进行初始化。UBL 对 DDR2 的初始化工作主要由 DEVICE_DDR2Init 函数完成,UBL 对 DDR2 的初始化流程图如图 4.7 所示。


首先,通过电源时钟管理模块 PSC 中的 DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_ENABLE)函数实现对 DDR2 模块的供电和时钟使能。其次,在上电或复位后,DM368 会对 VPT IO 进行校正以实现对 DDR2 模块 IO 输出阻抗的自动调节。在有关DDR2 硬件设计部分 3.3.2 节中,DDR_PADREFP 引脚通过精度为 0.5%、大小为 50 欧姆的高精度电阻接地为 VPT IO 的校正提供参考。等待 150 个时钟周期后完成对 VPTIO的 校 正 。 接 下 来 再 次 通 过 电源 时 钟 管 理 模 块 PSC 中 的 函 数 DEVICE_LPSCTTransition(LPSC_DDR2,0,PSC_SYNCRESET) 对 DDR2 进 行 同 步 重 启 , 通 过 函 数DEVICE_LPSCTTransition(LPSC_DDR2,0,PSC_ENABLE)对电源时钟模块重新使能。然后对 DDR2 的 PHY、参数、时钟和外围总线优先级进行设置。在 DDR2 的 PHY 设置中,配置 DDRPHYCR 寄存器为 0x000080C6,表示选择外部选通门,接收器断电。DDR2 参数的设置与 DM368 所外接的具体的 DDR2 芯片有关,根据实际所使用的 DDR2 芯片的页大小、块数和总线宽度对 DDR SDCR 寄存器进行设置。系统所选用的 DDR2 的页大小为 1024,共 8 块,使用的总线宽度为 16 位,列选通延迟为 5。根据以上分析将 DDRSDBCR 设置为0x00D34A32 和 0x0053CA32。 DDR2 的时钟配置通过 SDTIMR 和SDTIMR2 实现,分别设置为 0x45246412 和 0x4225C742。总线突发优先级设置中,对PBBPR 寄存器设置为 0x000000FE。刷新控制寄存器设置中通过对寄存器 SDRCR 设置为 0X83A 来实现。最后对 DDR2 同步重启,电源时装模块重新使能,返回到 DDR2 的初始化状态,初始化结束。

2.UBL 对 U-boot 的启动引导

UBL 在完成对底层硬件的初始化后,接下来就是将 NAND FLASH 中的 U-Boot 拷贝到 DDR2 内存中,引导 U-Boot 启动。UBL 对 U-Boot 启动引导流程图如图 4.8 所示。在 UBL 对 U-Boot 的有效描述符开始搜索之前,首先应该进行对描述符搜寻的起始地址和读取描述符所需内存空间进行设置。在此设置描述符的起始搜寻地址为BANK8~BANK10。使用函数 rxBuf=(Uint8*)UTIL_allocMem((APP_IMAGE_SIZE))对内存空间进行设置,在此对 APP_IMAGE_SIZE 的取值为( ( (Uint32)&DDRSize)>>3。在不超出最大内存值的情况下,为 U-Boot 的描述符分配 16MB 的内存空间以保证内存空间不会溢出。内存空间分配完成后,可通过函数 NAND_open 获取并配置 NAND Flash的信息,包括 NAND Flash 的块数和页数信息、起始地址、数据总线位数以及校验信息。上述配置工作完成后, UBL 通过函数 NAND_readPage 对 U-Boot 的描述符进行搜索,搜索的范围从起始块号的第 0 页开始直至最后块号的最后一页结束。当搜索到的描述符为有效时, UBL 会通过该有效描述符获取 U-Boot 向 DDR2 写入所需参数。通过该参数,UBL 可以完成以下两个任务:U-Boot 向 DDR2 内存的拷贝所需完成的准备工作以及分配 U-Boot 在 DDR2 内存中实际所在的空间位置。然后 UBL 通过函数 NAND_readPage将 U-Boot 搬运到 DDR2 内存中。搬运过程中的数据都会进行 ECC 校验纠错。如果数据拷贝失败,会进行第二次拷贝。如果第二次拷贝还未成功,UBL 会重新跳到 UBL 搜索页继续搜索 U-Boot 有效描述符,直到终止块号的最后一页完全拷贝到 DDR2 内存中。


五、U-Boot 启动过程分析

U-Boot(Univesal Boot Loader)是遵循 GPL 条款开发的开放源码项目[56],广泛应用于嵌入式系统开发中。它不仅支持嵌入式 Linux 系统的引导,同时还支持 NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS 等嵌入式操作系统,而且 U-Boot 对硬件的适应性也很好,支持 Power PC、MIPS、x86、ARM、NIOS、XScale 等多种处理器架
构[57]。
U-boot 具有以下优点:
1.源代码对外开放,开发人员可以进行修改并共享;
2.支持多种处理器系列和嵌入式操作系统。
3.技术成熟、性能稳定,以及非常高的可靠性。
4.功能设置灵活,引导产品需求与发布。
5.设备驱动源码丰富,支持常见的外围设备如:串口、以太网、SDRAM、FLASH、LCD、键盘、EEPROM 等。
6.开发调试文档非常丰富,网络支持强大。
U-Boot 的启动过程大致可分为两个阶段。第一阶段代码一般使用汇编语言代码实现,执行启动代码 start.s,主要完成部分硬件设备初始化、设置异常入口地址和异常处理函数、配置 PLL 确定系统主频、屏蔽中断、初始化 I/O 寄存器、关闭 MMU、初始化存储空间、加载 U-Boot 的第二段代码到 DDR 内存中、设置堆栈大小,并最终跳转到 C代码的 start_armboot()函数继续执行。第二阶段代码采用 C 语言实现,主要完成包括中断的处理、环境变量设置、串口设置等。采用 C 语言的好处是能够较好的实现复杂的功能,而且程序具有较高的可读性,代码的移植也比较方便。第一阶段主要功能主要是通过函数 start.s 来实现的,它对部分硬件设备进行初始化,函数 start.s 启动过程如下图 4.9 所示。


函数 start.s 首先进行复位使系统进入 SVC32 超级保护模式。接着对时钟和处理器的主频进行设置,依靠 cpu_init_crit 代码实现,cach 和 MMU 的关闭也依靠此代码实现。处理器工作频率、总线频率和存储器时钟频率的调节及初始化工作依据cpu_init_crit 代码中的函数 lowlevel_init 进行实现。函数 relocate 对 U-Boot 进行重新定位,完成第二阶段代码的拷贝工作。实际拷贝函数 copy_loop 将 U-Boot 程序拷贝到 TEXT_BASE 开始地址的空间中。初始化堆栈函数 stack_setup 实现对堆栈的初始化。函数 clear_bass 对未初始化全局变量的 bass 段进行初始化,通过函数 clbss_1 循环清除 bass。最后跳转到第二阶段起始代码入口函数_star_armboot。第二阶段中函数 start_armboot 是 Bootloader 代码中第一个被执行的 C 语言函数,它也是 C 语言代码的主函数。start_armboot 中的主要函数功能如下表 4.5 所示。



当所有的第一阶段 start.s 和第二阶段 start_armboot 函数代码执行完成后,系统进入main_loop 中,循环等待用户从串口输入的命令并执行该命令。