Porting uClinux to Samsung S3C44B0X Board

作者: Ryan SHENG ryansheng@sohu.com
一.Bootloader
理论上,uClinux引导时并非一定需要一个独立于Kernel ImageBootloader Image。然而,将BootloaderKernel分开设计能够使软件架构更加清晰,也有助于灵活地支持多种引导方式,实现一些有用的辅助功能。Bootloader的主要任务可以概括如下:

1.硬件初始化和系统引导;

2.加载uClinux Kernel Image (如果需要)

3.设置需要传递给Kernel的启动参数(如果需要)

4.调用uClinux Kernel

5.辅助功能:从主机下载新的Image

6.辅助功能:烧写Flash Memory

7.辅助功能:支持功能56所需的人机界面,如串行终端上的命令行接口。
对于常见的几类处理器内核,现在一般都找得到现成的Bootloader可用,不过需要针对具体的Board做些移植。在实现上述功能的前提下,也可以选择自行开发。由于Bootloader Image在物理上独立于Kernel Image,因此不一定选择GNU作为开发工具。对于以ARM7TDMI为内核的S 3C 44B0X处理器,完全可以使用ADS来开发Bootloader

1
.硬件初始化和系统引导
完整的Bootloader引导流程可描述如下:
硬件初始化阶段一 -> 复制二级Exception Vector Table -> 初始化各种处理器模式 -> 复制RORW,清零ZI -> (跳转到C代码入口函数) -> 初始化Exception/Interrupt Handler Entry Table -> 初始化Device Drivers -> 硬件初始化阶段二 -> 建立人机界面
下面对上述各步骤逐一加以说明。

1
1 硬件初始化阶段一
板子上电或复位后,程序从位于地址0x0Reset Exception Vector处开始执行,因此需要在这里放置Bootloader的第一条指令:b ResetHandler,跳转到标号为ResetHandler处进行第一阶段的硬件初始化,主要内容为:关Watchdog Timer,关中断,初始化PLL和时钟,初始化Memory Controller。比较重要的是PLL的输出频率要算正确,这里把它设置为50MHz;后面在计算SDRAMRefresh CountUARTBaud Rate等参数时还要用到。

1
2 复制二级Exception Vector Table
Exception Vector Table
BootloaderuClinux Kernel发生联系的地方之一(另两处是加载and/or调用Kernel,以及向Kernel传递启动参数)ARM7规定Exception Vector Table的基地址是0x0,所以Flash Memory的基地址也必须是0x0;而S 3C 44B0X处理器又不支持Memory Remap,这意味着无论运行什么样的上层软件,一旦发生中断,程序就得到Flash Memory中的Exception Vector Table里去打个转(中断Interrupt是异常Exception的一种)。对于uClinux而言,它会在RAM中建立自己的二级Exception Vector Table(后面将提到基地址被设为0x 0C 000000),所以在编写Bootloader时,地址0x0处的一级Exception Vector Table只需简单地包含向二级Exception Vector Table跳转的内容:

b ResetHandler ;Reset Handler
ldr pc,=0x 0c 000004 ;Undefined Instruction Handler
ldr pc,=0x 0c 000008 ;Software Interrupt Handler
ldr pc,=0x 0c 00000c ;Prefetch Abort Handler
ldr pc,=0x 0c 000010 ;Data Abort Handler
b .
ldr pc,=0x 0c 000018 ;IRQ Handler
ldr pc,=0x 0c 00001c ;FIQ Handler
LTORG

如果在Bootloader执行的全过程中都不必响应中断,那么上面的设置已能满足要求。但如果某些Bootloader功能要求使用中断(例如用Timer Interrupt实现精确定时,或利用External Interrupt支持Ethernet Driver以实现TFTP下载),那么Bootloader必须在同样的地址处配置自己的二级Exception Vector Table,以便同uClinux兼容。这张表事先存放在Flash Memory里,引导过程中由Bootloader将其复制到RAM地址0x 0C 000000
存放:
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, =0x 0c 000000
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) S 3C 44B0X 处理器在上电或复位后处于 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 之后 ) ,需要在这一步中填写,这里就不一一列出了。

另外, S 3C 44B0X 处理器的 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 S 3C 44B0X 处理器内置 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 的地址范围从 0x 0C 000000 0x0E000000

Flash Memory
0x00000000 ~ 0x00020000
存放 Bootloader
0x00020000 ~ 0x00200000
存放所有类型的 uClinux Kernel Image
运行 2.2 类型的 uClinux Kernel
运行 2.4 类型的自解压代码
SDRAM
0x 0C 008000 ~ xxxxxxxx
运行 Bootloader
0x 0C 200000 ~ xxxxxxxx
运行 2.1 2.3 2.4 类型 uClinux Kernel
0x 0C 400000 ~ 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 结束,代码示例如下。其中 0x 0C 000100 是内核启动参数在 RAM 中的基地址,指针 params 的类型是 struct tag 。宏 tag_next() 以指向当前标记的指针为参数,计算下一个标记的起始地址。

params = (struct tag *)0x 0C 000100;
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-s 3c 44b0/arch.c 中设置内核启动参数在 RAM 中的基地址。如果 Kernel 不需要从 Bootloader 接收启动参数,下面代码中的“ BOOT_PARAMS(0x 0C 000100) ”这一行可以不写。


MACHINE_START(S 3C 44B0, "44B0EVAL")
MAINTAINER("XXX YYY")
BOOT_MEM(DRAM_BASE,0x00000000,0x00000000)
BOOT_PARAMS(0x 0C 000100)
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(S 3C 44B0X Machine Type ID 定义在 include/asm-arm/mach-types.h 中: #define MACH_TYPE_S 3C 44B0 178 。寄存器 r1 也可以在 Kernel 启动之初的 head-armv.S 中设置 )
禁止中断 (IRQs FIQs)
CPU
运行在 SVC 模式;
MMU
必须关闭 (S 3C 44B0X 没有 MMU)
指令 Cache 可以打开也可以关闭,数据 Cache 必须关闭 (S 3C 44B0X 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. S 3C 44B0X User's Manual Samsung
2
.嵌入式系统 Boot Loader 技术内幕 詹荣开  

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值