摘 要:在嵌入式系统的开发中,首先移植一个稳定且功能强大的 U-Boot
对后续软件的开发至关重要。本文将详细介绍 U-Boot
在 S3C2410 开发板上的移植与运行。
关键词:嵌入式系统; U-Boot
;移植
引言
U-Boot 是用于初始化目标板硬件,为嵌入式操作系统提供目标板硬件配置信息,完成嵌入式操作系统装载、引导和运行的固件程序。它能够将系统的软硬件紧密衔接在一起。
S3C2410 是三星公司的一款基于ARM920T核的嵌入式通用处理器。本文将详细介绍
U-Boot 在
S3C2410 开发板上的移植与运行。
U-Boot
简介
U-Boot 支持ARM
、 PowerPC等多种架构的处理器,也支持 Linux 、 NetBSD 和 VxWorks 等多种操作系统。它提供启动加载和下载两种工作模式。启动加载模式也称自主模式,一般是将存储在目标板Flash中的内核和文件系统的镜像装载到SDRAM中,整个过程无需用户的介入。在使用嵌入式产品时,一般工作在该模式下。工作在下载模式时,目标板往往受外设
( 一般是 PC 机 ) 的控制,从而将外设中调试好的内核和文件系统下载到目标板中去。 U-Boot
允许用户在这两种工作模式间进行切换。通常目标板启动时,会延时等待一段时间,如果在设定的延时时间范围内,用户没有按键,
U-Boot 就进入启动加载模式。
开发板的主要配置包括三星ARM9处理器 S3C2410
、 1 个串口和 JTAG 接口,晶振为12MHz ,系统主频为200MHz 。另外,开发板上还包括 1 片 4M
×16 位数据宽度的Flash,地址范围为 0x01000000 ~ 0x01800000 和 2 片 8M ×16 位数据宽度的
SDRAM ,地址范围为 0x30000000 ~ 0x32000000 。 Flash 使用了 2410 处理器的BANK0单元,由于2410中地址是循环映射的,因而
0x01000000 和 0x0 地址等同。
在本系统中, U-Boot
的主要功能包括:建立和初始化 RAM ;初始化一个串口;检测机器的体系结构,传递 MACH_TYPE_xxx 的值
(SMDK2410) 给内核;建立内核的标记列表 (tagged list) ;调用内核镜像。
U-Boot
移植步骤
为了使 U-Boot
支持新的开发板,一种简便的做法是在 U-Boot
已经支持的开发板中选择一种和目标板接近的,并在其基础上进行修改。代码修改的步骤如下:
- 在 board 目录下创建 smdk2410 目录,添加 smdk2410.c 、 flash.c 、 memsetup.s
、 u-boot.lds 和 config.mk 等;
- 在 cpu 目录下创建 arm920t 目录,主要包含 start.s 、 interrupts.c 、 cpu.c
、 serial.c 和 speed.c 等文件;
- 在 include/configs 目录下添加 smdk2410.h ,它定义了全局的宏定义等;
- 修改 u-boot 根目录下的 Makefile 文件:
smdk2410_config : unconfig@./mkconfig $(@:_config=) arm arm920t
smdk2410
- 运行 make smdk2410_config ,如果没有错误,就可以开始进行与硬件相关的代码移植工作。由于这部分代码与硬件紧密相关,所以要熟悉开发板的硬件配置,可参考各芯片的用户手册。
U-Boot
启动过程
U-Boot 的启动过程可以分成
3 个阶段。首先在 Flash 中运行汇编程序,将 Flash 中的启动代码部分复制到 SDRAM 中,同时创造环境准备运行
C 程序;然后在 SDRAM 中执行,对硬件进行初始化;最后设置内核参数的标记列表,复制镜像文件,进入内核的入口函数。
- 程序首先在 Flash 中运行 CPU 入口函数 /cpu/arm920t/start.s 。具体工作包括:设置异常的入口地址和异常处理函数;配置
PLLCON 寄存器,确定系统的主频;屏蔽看门狗和中断;初始化 I/O 寄存器;关闭 MMU 功能;调用 /board/smdk2410
中的 memsetup.s ,初始化存储器空间,设置刷新频率;将 U-Boot 的内容复制到 SDRAM 中;设置堆栈的大小,
ldr pc, _start_armboot 。
board/s 3c 2410 中 config.mk 文件 (TEXT_BASE = 0x 31F 00000) 用于设置程序编译连接的起始地址,在程序中要特别注意与地址相关指令的使用。
当程序在 Flash 中运行时,执行程序跳转时必须要使用跳转指令,而不能使用绝对地址的跳转 ( 即直接对 PC 操作 )
。如果使用绝对地址,那么,程序的取指是相对于当前 PC 位置向前或者向后的 32MB 空间内,而不会跳入 SDRAM 中。
- 程序跳转到 SDRAM 中执行 /lib_arm/board.c 中的 start_armboot() 函数。该函数将完成如下工作:
* 设置通用端口 rGPxCON;rGPxUP ;设置处理器类型 gd->bd->bi_arch_number
= 193 ;设置启动参数地址 gd->bd->bi_boot_params = 0x30000100 ;
* env_init :设置环境变量,初始化环境;
* init_baudrate :设置串口的波特率;
* serial_init :设置串口的工作方式;
* flash_init :设置 ID 号、每个分页的起始地址等信息,将信息送到相应的结构体中;
* dram_init :设置 SDRAM 的起始地址和大小;
* env_relocate :将环境变量的地址送到全局变量结构体中 (gd->env_addr = (ulong)&(env_ptr->data))
;
* enable_interrupts :开启中断;
* main_loop :该函数主要用于设置延时等待,从而确定目标板是进入下载操作模式还是装载镜像文件启动内核。在设定的延时时间范围内,目标板将在串口等待输入命令,当目标板接到正确的命令后,系统进入下载模式。在延时时间到达后,如果没有接收到相关命令,系统将自动进入装载模式,执行
bootm 30008000 30800000 命令,程序进入 do_bootm_linux() 函数,调用内核启动函数;
- 装载模式下系统将执行 do_bootm_linux() 函数, 0x30008000 是内核在 SDRAM 中的起始地址;
0x30800000 是 ramdisk 在 SDRAM 中的起始地址; 0x40000 是内核在 Flash 中的位置,
0x100000 是数据块的大小; 0x140000 是 ramdisk 在 FLASH 中的位置, 0x440000
是数据块的大小。系统调用 memcpy() 函数将内核从 flash 和 ramdisk 复制到 SDRAM 中,具体如下:
memcpy((void *)0x30008000, (void *)0x40000, 0x100000) ; // 复制数据块
memcpy((void *)0x30800000, (void *)0x140000, 0x440000) ; //
复制数据块
通常,将内核参数传递给 Linux 操作系统有两种方法:采用 struct param_struct 结构体或标记列表。本系统中采用了第二种方法。
一个合法的标记列表开始于 ATAG_CORE ,结束于 ATAG_NONE 。 ATAG_CORE 可以为空,一个空的
ATAG_CORE 的 size 字段设为 “ 2” (0x00000002) 。 ATAG_NONE 的 size 字段必须设为
“ 0” 。标记列表可以有任意多的标记 (tag) 。在嵌入式 Linux 系统中,通常由 U-Boot 设置的启动参数有:
ATAG_CORE 、 ATAG_MEM 、 ATAG_CMDLINE 、 ATAG_RAMDISK 、 ATAG_INITRD
等。
在本系统中,传递参数时分别调用了以下 tag :
setup_start_tag(bd) ; // 标记列表开始
setup_memory_tags(bd); // 设置内存的起始位置和大小
setup_commandline_tag(bd, commandline); /*Linux 内核在启动时可以命令行参数的形式来接收信息,利用这一点可以向内核提供那些内核不能检测的硬件参数信息,或者重载
(override) 内核检测到的信息,这里 char *commandline "initrd=0x30800000,0x440000
root=/dev/ram init=/linuxrc console=ttyS0" ; */
setup_ramdisk_tag(bd); // 表示内核解压后 ramdisk 的大小
setup_initrd_tag(bd, initrd_start, initrd_end); // 设置 ramdisk
的大小和物理起始地址
setup_end_tag(bd); // 标记列表结束
其中 bd_t *bd = gd->bd 是指向 bd_t 结构体的指针,在该结构体中存放了关于开发板配置的基本信息。标记列表应该放在内核解压和
initrd 的 bootp 程序都不会覆盖的内存区域,同时又不能和异常处理的入口地址相冲突。建议放在 RAM 起始的
16K 大小处 , 在本系统中即为 0x30000100 处。
U-Boot
调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START + 0x8000 地址处。在跳转时,要满足下列条件:
a) CPU 寄存器的设置: R0 = 0 ; R1 =机器类型 ID ,本系统的机器类型 ID = 193 。 R2
=启动参数标记列表在 RAM 中的起始基地址;
b) CPU 模式:必须禁止中断 (IRQs 和 FIQs) ; CPU 必须工作在 SVC 模式;
c) Cache 和 MMU 的设置: MMU 必须关闭;指令 Cache 可以打开也可以关闭;数据 Cache 必须关闭。
系统采用下列代码来进入内核函数:
theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);
theKernel(0, bd->bi_arch_number); 其中, hdr 是 image_header_t
类型的结构体, hdr->ih_ep 指向内核的第一条指令地址,即 Linux 操作系统下的 /kernel/arch/arm/boot/compressed/head.S
汇编程序。 theKernel() 函数调用应该不会返回,如果该调用返回,则说明出错。
结语
本文总结介绍了 U-Boot
在 S3C2410 上的移植,移植完成后,
U-Boot 能够稳定地运行在开发板上,为后续的软件开发打下较好的基础。 |