Porting uClinux to Samsung S3C44B0X Board |
一.Bootloader 理论上,uClinux引导时并非一定需要一个独立于Kernel Image的Bootloader Image。然而,将Bootloader与Kernel分开设计能够使软件架构更加清晰,也有助于灵活地支持多种引导方式,实现一些有用的辅助功能。Bootloader的主要任务可以概括如下: 1.硬件初始化和系统引导; 2.加载uClinux Kernel Image (如果需要); 3.设置需要传递给Kernel的启动参数(如果需要); 4.调用uClinux Kernel; 5.辅助功能:从主机下载新的Image; 6.辅助功能:烧写Flash Memory; 7.辅助功能:支持功能5和6所需的人机界面,如串行终端上的命令行接口。 对于常见的几类处理器内核,现在一般都找得到现成的Bootloader可用,不过需要针对具体的Board做些移植。在实现上述功能的前提下,也可以选择自行开发。由于Bootloader Image在物理上独立于Kernel Image,因此不一定选择GNU作为开发工具。对于以ARM7TDMI为内核的S3C44B0X处理器,完全可以使用ADS来开发Bootloader。 1.硬件初始化和系统引导 完整的Bootloader引导流程可描述如下: 硬件初始化阶段一 -> 复制二级Exception Vector Table -> 初始化各种处理器模式 -> 复制RO和RW,清零ZI -> (跳转到C代码入口函数) -> 初始化Exception/Interrupt Handler Entry Table -> 初始化Device Drivers -> 硬件初始化阶段二 -> 建立人机界面 下面对上述各步骤逐一加以说明。 1.1 硬件初始化阶段一 板子上电或复位后,程序从位于地址0x0的Reset Exception Vector处开始执行,因此需要在这里放置Bootloader的第一条指令:b ResetHandler,跳转到标号为ResetHandler处进行第一阶段的硬件初始化,主要内容为:关Watchdog Timer,关中断,初始化PLL和时钟,初始化Memory Controller。比较重要的是PLL的输出频率要算正确,这里把它设置为50MHz;后面在计算SDRAM的Refresh Count和UART的Baud Rate等参数时还要用到。 1.2 复制二级Exception Vector Table Exception Vector Table是Bootloader与uClinux Kernel发生联系的地方之一(另两处是加载and/or调用Kernel,以及向Kernel传递启动参数)。ARM7规定Exception Vector Table的基地址是0x0,所以Flash Memory的基地址也必须是0x0;而S3C44B0X处理器又不支持Memory Remap,这意味着无论运行什么样的上层软件,一旦发生中断,程序就得到Flash Memory中的Exception Vector Table里去打个转(中断Interrupt是异常Exception的一种)。对于uClinux而言,它会在RAM中建立自己的二级Exception Vector Table(后面将提到基地址被设为0x0C000000),所以在编写Bootloader时,地址0x0处的一级Exception Vector Table只需简单地包含向二级Exception Vector Table跳转的内容: b ResetHandler ;Reset Handler ldr pc,=0x0c000004 ;Undefined Instruction Handler ldr pc,=0x0c000008 ;Software Interrupt Handler ldr pc,=0x0c00000c ;Prefetch Abort Handler ldr pc,=0x0c000010 ;Data Abort Handler b . ldr pc,=0x0c000018 ;IRQ Handler ldr pc,=0x0c00001c ;FIQ Handler LTORG 如果在Bootloader执行的全过程中都不必响应中断,那么上面的设置已能满足要求。但如果某些Bootloader功能要求使用中断(例如用Timer Interrupt实现精确定时,或利用External Interrupt支持Ethernet Driver以实现TFTP下载),那么Bootloader必须在同样的地址处配置自己的二级Exception Vector Table,以便同uClinux兼容。这张表事先存放在Flash Memory里,引导过程中由Bootloader将其复制到RAM地址0x0C000000: 存放: RelocatedExceptionVectorStart mov pc,#0 b HandlerUndef b HandlerSWI b HandlerPAbort b HandlerDAbort b . b HandlerIRQ b HandlerFIQ HandlerUndef HANDLER HandleUndef HandlerSWI HANDLER HandleSWI HandlerPAbort HANDLER HandlePAbort HandlerDAbort HANDLER HandleDAbort HandlerIRQ HANDLER HandleIRQ HandlerFIQ HANDLER HandleFIQ LTORG RelocatedExceptionVectorEnd 复制: adr r0, RelocatedExceptionVectorStart ldr r2, =0x0c000000 adr r3, RelocatedExceptionVectorEnd 0 cmp r0, r3 ldrcc r1, [r0], #4 strcc r1, [r2], #4 bcc %B0 其中HANDLER是一个宏,用于查找Exception Handler Routines的入口地址。这些地址存放在由HandleXXX指向的表项中,该表定位在RAM高端,基地址为_ISR_STARTADDRESS: ^ _ISR_STARTADDRESS HandleReset # 4 HandleUndef # 4 HandleSWI # 4 HandlePAbort # 4 HandleDAbort # 4 HandleReserved # 4 HandleIRQ # 4 HandleFIQ # 4 该表的内容将在步骤1.5:“初始化Exception/Interrupt Handler Entry Table”中被填写为各Exception Handler Routine的入口地址。 1.3 初始化各种处理器模式 ARM7TDMI支持7种Operation Mode:User,FIQ,IRQ,Supervisor,Abort,System和Undefined。Bootloader需要依次切换到每种模式,初始化其程序状态寄存器(SPSR)和堆栈指针(SP)。S3C44B0X处理器在上电或复位后处于Supervisor模式,本步骤中把对Supervisor模式的初始化放在最后,也就是说Bootloader的后续部份仍将运行在Supervisor模式下。 1.4 复制RO和RW,清零ZI 对于ADS开发工具而言,一个ARM程序由RO,RW和ZI三个段组成,其中RO为代码段,RW是已初始化的全局变量,ZI是未初始化的全局变量(对于GNU工具,对应的概念是TEXT,DATA和BSS)。RO段既可以在Flash Memory中运行,也可以在RAM中运行。考虑到Bootloader中可能需要烧写Flash Memory,因此在引导阶段应当将RO和RW段复制到RAM中,并将ZI段清零。当RO段复制完毕之后,程序就可以跳转到RAM中运行。若不考虑烧写Flash Memory,则可以不复制RO段,程序始终在Flash Memory中运行。ADS使用下列Linker Symbols来记录各段的起始和结束地址: |Image$$RO$$Base| :RO段起始地址 |Image$$RO$$Limit| :RO段结束地址加1 |Image$$RW$$Base| :RW段起始地址 |Image$$RW$$Limit| :ZI段结束地址加1 |Image$$ZI$$Base| :ZI段起始地址 |Image$$ZI$$Limit| :ZI段结束地址加1 可以在程序中引用这些标号。需要注意的是,这些标号的值是根据ARM Linker中RO Base和RW Base的设置来计算的,属于“Linker Address”或“Execution Address”,并不一定代表这些段存放在Flash Memory中的地址,在编写复制程序时需要根据具体情况作相应的计算。 1.5 初始化Exception/Interrupt Handler Entry Table 在步骤1.2里已经提到,需要在这一步中填写各Exception Handler Routine的入口地址。由于IRQ Exception为全部的中断所共用,因此必须在IRQ Exception Handler Routine中根据中断状态寄存器判断中断源,并调用相应的Interrupt Handler Routine。各Interrupt Handler Routine的入口地址也存放在上述的Exception/Interrupt Handler Entry Table中(紧接在HandleFIQ之后),需要在这一步中填写,这里就不一一列出了。 另外,S3C44B0X处理器的Interrupt Controller支持两种中断处理模式:Vectored Mode和Non-Vectored Mode,其中前者可能是Samsung特有的模式,并不被其它ARM7内核所支持。考虑到代码的可移植性,以上讨论仅针对这里所使用的Non-Vectored Mode。 1.6 初始化Device Drivers 在这一步中需要为Bootloader用到的一些关键Device Drivers建立必要的数据结构,主要包括用于精确定时的Watchdog Timer Driver和用于支持串行终端的UART Driver。 1.7 硬件初始化阶段二 继续对硬件进行初始化,主要包括:GPIO,Cache,Interrupt Controller,Watchdog Timer和UARTs。S3C44B0X处理器内置data/instruction合一的8KB Cache,且允许按地址范围设置两个Non-Cacheable区间。合理的配置是打开对RAM区间的Cache,关闭对其它地址区间(包含Flash Memory区间)的Cache。所有硬件初始化完毕之后,开中断。 在步骤1.6和1.7中,仍然遵循“必要”的原则对硬件和Device Drivers进行初始化。在目前阶段没有涉及的设备(如Ethernet Controller),可以留待使用它们之前再进行初始化。 1.8 建立人机界面 引导过程的最后一步是在串行终端上建立人机界面,并等待用户输入命令。合理的做法是先等待固定的秒数,若在此期间未接收到用户输入,则直接从Flash Memory中加载and/or调用uClinux Kernel。若接收到用户输入,则显示菜单模式或命令行模式的交互界面,等待用户进一步的命令。这里就不对此详细讨论了。 2.加载Kernel Bootloader是否需要执行加载操作,取决于uClinux Kernel Image的类型。根据不同的配置,可以生成下面几种uClinux Kernel Image: 2.1 非压缩,非XIP XIP(eXecute In Place)是指不对代码段重新定位,在存放代码段的位置就地运行程序。该类型的uClinux Kernel Image以非压缩格式存放在Flash Memory中,由Bootloader加载到RAM中并直接调用。 2.2 非压缩,XIP 该类型的uClinux Kernel Image以非压缩格式存放在Flash Memory中,不需加载,由Bootloader直接调用。复制init段和data段,清零bss段的工作由Kernel自行完成。 2.3 RAM自解压 压缩格式的uClinux Kernel Image都是由开头的一段自解压代码和后面的压缩数据部分组成。对于Kernel而言,由于是以压缩格式存放,因次只能以非XIP方式执行。RAM自解压类型的uClinux Kernel Image存放在Flash Memory中,由Bootloader加载到RAM中的一段临时空间,然后调用其自解压代码。可执行的uClinux Kernel被解压到最终的执行空间,然后运行。压缩格式Image所占据的临时RAM空间可在随后由uClinux回收利用。 2.4 ROM自解压 解压缩操作也可以在Flash Memory中完成。实际上,这意味着以XIP方式执行自解压代码。ROM自解压类型的uClinux Kernel Image存放在Flash Memory中,不需加载,由Bootloader直接调用其自解压代码。自解压代码自行复制其data段,清零bss段,然后将可执行的uClinux Kernel解压到最终的执行空间并运行之。与RAM自解压相比,用ROM自解压方式引导uClinux并不真正节省RAM,而且在Flash Memory中解压缩速度较慢,因此实用价值不大。 2.5 Memory Map 下面给出Bootloader和uClinux在存储和运行时的Memory Map。系统存储器由NOR Flash Memory (2MB)和SDRAM(32MB)组成,Flash Memory的地址范围从0x0到0x00200000,SDRAM的地址范围从0x0C000000到0x0E000000。 Flash Memory 0x00000000 ~ 0x00020000: 存放Bootloader 0x00020000 ~ 0x00200000: 存放所有类型的uClinux Kernel Image 运行2.2类型的uClinux Kernel 运行2.4类型的自解压代码 SDRAM 0x0C008000 ~ xxxxxxxx: 运行Bootloader 0x0C200000 ~ xxxxxxxx: 运行2.1,2.3,2.4类型uClinux Kernel 0x0C400000 ~ xxxxxxxx: 临时存放2.3类型压缩Image,并运行自解压 3.设置内核启动参数 Linux 2.4.x以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。每个标记存放在一个tag结构中,每个tag结构由标识被传递参数的tag_header结构以及随后存放的参数值组成。数据结构tag、tag_header以及各种参数的数据结构都定义在Linux内核源码的头文件linux/include/asm-armnommu/setup.h中。 通常需要由Bootloader设置的启动参数有:ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_SERIAL、ATAG_INITRD等。启动参数的标记列表以ATAG_CORE开始,以ATAG_NONE结束,代码示例如下。其中0x0C000100是内核启动参数在RAM中的基地址,指针params的类型是struct tag。宏tag_next()以指向当前标记的指针为参数,计算下一个标记的起始地址。 params = (struct tag *)0x0C000100; params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size(tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next(params); …… params->hdr.tag = ATAG_NONE; params->hdr.size = 0; 在Linux内核源码的linux/arch/armnommu/mach-s3c44b0/arch.c中设置内核启动参数在RAM中的基地址。如果Kernel不需要从Bootloader接收启动参数,下面代码中的“BOOT_PARAMS(0x0C000100)”这一行可以不写。 MACHINE_START(S3C44B0, "44B0EVAL") MAINTAINER("XXX YYY") BOOT_MEM(DRAM_BASE,0x00000000,0x00000000) BOOT_PARAMS(0x0C000100) INITIRQ(genarch_init_irq) MACHINE_END uClinux Kernel处理启动参数时的代码调用关系可查阅linux/init/main.c和linux/arch/armnommu/kernel/setup.c:start_kernel()àsetup_arch()àparse_tags()。parse_tags()函数中调用parse_tag()函数依次处理每个标记。parse_tag()函数先判断tag_header结构中的标记类型,然后调用相应的处理函数。例如,调用parse_tag_cmdline()处理ATAG_CMDLINE标记,调用parse_tag_initrd()处理ATAG_INITRD标记,等等。对应关系如下: static const struct tagtable core_tagtable[] __init = { { ATAG_CORE, parse_tag_core}, { ATAG_MEM, parse_tag_mem32}, { ATAG_VIDEOTEXT, parse_tag_videotext}, { ATAG_RAMDISK, parse_tag_ramdisk}, { ATAG_INITRD, parse_tag_initrd}, { ATAG_SERIAL, parse_tag_serialnr}, { ATAG_REVISION, parse_tag_revision}, { ATAG_CMDLINE, parse_tag_cmdline} }; 对于Kernel Command Line,parse_tag_cmdline()函数将用内核参数表中的命令字符串来覆盖default_command_line[]变量。如果Kernel不从Bootloader接收启动参数,也可以有两种方法来初始化Kernel Command Line。在linux/arch/armnommu/kernel/setup.c中有: static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; 因此一种方法是在make menuconfig时通过修改“General Setup”子菜单中的“Default kernel command string”选项来定义linux/include/linux/autoconf.h头文件中的CONFIG_CMDLINE宏,另一种方法是在linux/arch/armnommu/kernel/setup.c中直接把default_command_line[]写死。 4.调用Kernel Bootloader调用uClinux Kernel的方法是直接跳转到Kernel的第一条指令处。在跳转时要满足下列条件: CPU寄存器r0=0; CPU寄存器r1=Machine Type ID(S3C44B0X的Machine Type ID定义在include/asm-arm/mach-types.h中:#define MACH_TYPE_S3C44B0 178。寄存器r1也可以在Kernel启动之初的head-armv.S中设置); 禁止中断(IRQs和FIQs); CPU运行在SVC模式; MMU必须关闭(S3C44B0X没有MMU); 指令Cache可以打开也可以关闭,数据Cache必须关闭(S3C44B0X的Cache是指令与数据合一的,因此只能选择关闭)。 C代码调用Kernel的示例如下,其中r0和r1的值通过参数传递: void (*CallKernel)(int zero, int mach) = (void (*)(int, int))KERNEL_ADDR; CallKernel(0, 178); 5.辅助功能 完整的Bootloader还应该支持从主机下载文件到目标板的RAM;用RAM中的数据烧写Flash Memory;以及上述功能所需的人机交互接口。文件下载途径视目标板所提供的物理通讯接口而定,比较简单的方法一般是通过串口,用Xmodem或Ymodem协议下载,但速度较慢。目标板上只需要实现协议的接收部份,主机上可以用HyperTerminal等工具来发送文件。如果目标板提供Ethernet等快速接口,也可以移植一个简单的TCP/IP栈,用TFTP等标准文件传输协议下载。目前Flash Memory一般都是用NOR Flash,烧写是非常简单的;需要注意的是对于多数Flash芯片,在Erase/Program之前需要先Unprotect。人机交互接口不难在串行终端上实现,这里就不赘述了。 参考文献: 1. S3C44B0X User's Manual Samsung 2.嵌入式系统Boot Loader技术内幕 詹荣开 |
文章评论(0条评论)
登录后参与讨论