uboot代码详细分析.pdf

目录
u-boot-1.1.6 之cpu/arm920t/start.s 分析 ........................................................................................... 2
u-boot 中.lds 连接脚本文件的分析 ...................................................................................................12
分享一篇我总结的uboot 学习笔记(转) .....................................................................................15
U-BOOT 内存布局及启动过程浅析 ...................................................................................................22
u-boot 中的命令实现 .......................................................................................................................... 25
U-BOOT 环境变量实现 ........................................................................................................................28
1.相关文件 ...................................................................................................................................28
2.数据结构 ...................................................................................................................................28
3.ENV 的初始化...........................................................................................................................30
3.1env_init ............................................................................................................................30
3.2 env_relocate ...................................................................................................................30
3.3*env_relocate_spec ........................................................................................................31
4. ENV 的保存 ..............................................................................................................................31
U-Boot 环境变量 ..........................................................................................................................32
u-boot 代码链接的问题 ......................................................................................................................35
ldr 和adr 在使用标号表达式作为操作数的区别 ............................................................................40
start_armboot 浅析 ..............................................................................................................................42
1.全局数据结构的初始化 ..........................................................................................................42
2.调用通用初始化函数...............................................................................................................43
3.初始化具体设备 .......................................................................................................................44
4.初始化环境变量 .......................................................................................................................44
5.进入主循环 ...............................................................................................................................44
u-boot 编译过程 ..................................................................................................................................44
mkconfig 文件的分析 .......................................................................................................................... 47
从NAND 闪存中启动U-BOOT 的设计 ..............................................................................................50
引言 ..............................................................................................................................................50
NAND 闪存工作原理 ................................................................................................................... 51
从NAND 闪存启动U-BOOT 的设计思路.................................................................................. 51
具体设计 ...................................................................................................................................... 51
支持NAND 闪存的启动程序设计 ..................................................................................... 51
支持U-BOOT 命令设计 ...................................................................................................... 52
结语 .............................................................................................................................................. 53
参考文献 ...................................................................................................................................... 53
U-boot 给kernel 传参数和kernel 读取参数—struct tag (以及补充) ............................................ 53
1 、u-boot 给kernel 传RAM 参数 ........................................................................................54
2 、Kernel 读取U-boot 传递的相关参数 .............................................................................56
3 、关于U-boot 中的bd 和gd...............................................................................................59
U-BOOT 源码分析及移植 ....................................................................................................................60
一、u-boot 工程的总体结构: ..................................................................................................61
1、源代码组织 ....................................................................................................................61
2.makefile 简要分析 ............................................................................................................61
3、u-boot 的通用目录是怎么做到与平台无关的?......................................................63
4、smkd2410 其余重要的文件 : ...................................................................................63
二、u-boot 的流程、主要的数据结构、内存分配 ................................................................64
1、u-boot 的启动流程: ...................................................................................................64
2、u-boot 主要的数据结构 ...............................................................................................66
3、u-boot 重定位后的内存分布: ...................................................................................68
三、u-boot 的重要细节 。 ........................................................................................................68
关于U-boot 中命令相关的编程 : ................................................................................. 73
四、U-boot 在ST2410 的移植,基于NOR FLASH 和NAND FLASH 启动。......................... 76
1、从smdk2410 到ST2410: .............................................................................................. 76
2、移植过程: .................................................................................................................... 76
3、移植要考虑的问题: ...................................................................................................77
4、SST39VF1601: .................................................................................................................77
5、我实现的flash.c 主要部分: ...................................................................................... 78
6、增加从Nand 启动的代码 : ..................................................................................... 82
7、添加网络命令。 ............................................................................................................ 87
u-boot-1.1.6 之cpu/arm920t/start.s 分析
/*
* armboot - Startup Code for ARM920 CPU-core
*
* Copyright (c) 2001 Marius Gr 鰃er < [email protected]>
* Copyright (c) 2002 Alex Z 黳ke < [email protected]>
* Copyright (c) 2002 Gary Jennejohn < [email protected]>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include
#include
/*
*************************************************************
************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************
************
*/
//global 声明一个符号可被其他文档引用,相当于声明了一个全局变量,.globl 和.global 相同。
//该部分为处理器的异常处理向量表。地址范围为0x0000 0000 ~ 0x0000 0020,刚好8 条指令。
.globl _start //u-boot 启动入口
_start: b reset //复位向量并且跳转到reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq //中断向量
ldr pc, _fiq //中断向量
// .word 伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr 初始
化。.long 和.int 作用与之相同。
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
// .align 伪操作用于表示对齐方式:通过添加填充字节使当前位置满足一定的对齐方式。.balign 的作用
同.align。
// .align {alignment} {,fill} {,max}
// 其中:alignment 用于指定对齐方式,可能的取值为2 的次幂,缺省为4。fill 是填充内容,缺省用0
填充。max 是填充字节数最大值,假如填充字节数超过max,
// 就不进行对齐,例如:
// .align 4 /* 指定对齐方式为字对齐 */
.balignl 16,0xdeadbeef
/*
*************************************************************
************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************
************
*/
// TEXT_BASE 在研发板相关的目录中的config.mk 文档中定义, 他定义了
// 代码在运行时所在的地址, 那么_TEXT_BASE 中保存了这个地址
_TEXT_BASE:
.word TEXT_BASE
// 声明 _armboot_start 并用 _start 来进行初始化,在board/u-boot.lds 中定义。
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
// 声明_bss_start 并用__bss_start 来初始化,其中__bss_start 定义在和板相关的u-boot.lds 中。
// _bss_start 保存的是__bss_start 这个标号所在的地址, 这里涉及到当前代码所在
// 的地址不是编译时的地址的情况, 这里直接取得该标号对应的地址, 不受编译时
// 地址的影响. _bss_end 也是同样的道理.
.globl _bss_start
_bss_start:
.word __bss_start
// 同上
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/*
* the actual reset code
*/
// MRS {} Rd,CPSR|SPSR 将CPSR|SPSR 传送到Rd
// 使用这两条指令将状态寄存器传送到一般寄存器,只修改必要的位,再将结果传送回状态寄存器,这
样能够最好地完成对CRSP 或SPSR 的修改
// MSR {} CPSR_|SPSR_,Rm 或是 MSR {} CPSR_f|SPSR_f,#
// MRS 和MSR 配合使用,作为更新PSR 的“读取--修改--写回”序列的一部分
// bic r0,r1,r2 ;r0:=r1 and not r2
// orr ro,r1,r2 ;r0:=r1 or r2
// 这几条指令执行完毕后,进入SVC32 模式,该模式主要用来处理软件中断(SWI)
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr //将CPSR 状态寄存器读取,保存到R0 中
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0 //将R0 写入状态寄存器中
/* turn off the watchdog */
//关闭看门狗
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
//关闭所有中断
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
// B 转移指令,跳转到指令中指定的目的地址
// BL 带链接的转移指令,像B 相同跳转并把转移后面紧接的一条指令地址保存到链接寄存器LR(R14)
中,以此来完成子程式的调用
// 该语句首先调用cpu_init_crit 进行CPU 的初始化,并把下一条指令的地址保存在LR 中,以使得执
行完后能够正常返回。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
//调试阶段的代码是直接在RAM 中运行的,而最后需要把这些代码固化到Flash 中,因此U-Boot 需要
自己从Flash 转移到
//RAM 中运行,这也是重定向的目的所在。
//通过adr 指令得到当前代码的地址信息:假如U-boot 是从RAM 开始运行,则从adr,r0,_start 得到
的地址信息为
//r0=_start=_TEXT_BASE=TEXT_BASE=0xa3000000;假如U-boot 从Flash 开始运行,即从处
理器对应的地址运行,
//则r0=0x0000,这时将会执行copy_loop 标识的那段代码了。
// _TEXT_BASE 定义在board/smdk2410/config.mk 中
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
//重新定位代码
//声明_bss_start 并用__bss_start 来初始化,其中__bss_start 定义在和板相关的u-boot.lds 中
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack */
//初始化堆栈
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
#if 0
/* try doing this stuff after the relocation */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMR
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
/* END stuff after relocation */
#endif
//跳转到start_armboot 函数入口,_start_armboot 字保存函数入口指针
//start_armboot 函数在lib_arm/board.c 中实现
ldr pc, _start_armboot
_start_armboot: .word start_armboot
/*
*************************************************************
************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************
************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
//初始化CACHE
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
//关闭MMU 和CACHE
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
//初始化RAM 时钟。因为内存时钟是依赖开发板硬件的,所以在board 的相应目录下可以找到
memsetup.s 文件
mov ip, lr
bl lowlevel_init //lowlevel_init 子程序在board/smdk2410/memsetup.s 中实现
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/*
*************************************************************
************
*
* Interrupt handling
*
*************************************************************
************
*/
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
ldmia r2, {r2 - r3} @ get pc, cpsr
add r0, sp, #S_FRAME_SIZE @ restore sp_SVC
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort
stack
str lr, [r13] @ save caller lr / spsr
mrs lr, spsr
str lr, [r13, #4]
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13
mov lr, pc
movs pc, lr
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
//以下都是中断处理函数,具体实现在lib_arm 目录下interrupts.c
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif
u-boot 中.lds 连接脚本文件的分析
对于.lds 文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个
段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。
先看一下GNU 官方网站上对.lds 文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的
某段(代码段、数据段等)
3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也
是start。GNU 网站上说start 可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址。
看一个简单的例子:(摘自《2410 完全开发》)
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o 放在0x00000000 地址开始处,init.o 放在head.o 后面,他们的运行地址
也是0x00000000,即连接和存储地址相同(没有AT 指定);main.o 放在4096(0x1000,
是AT 指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0
x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。
这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds 连
接脚本文件中分别指定。
编写好的.lds 文件,在用arm-linux-ld 连接命令时带-Tfilename 来调用执行,如
arm-linux-ld –Tnand.lds x.o y.o –o xy.o。也用-Ttext 参数直接指定连接地址,如
arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘
记了也可查看,以前不少东西没记下来现在忘得差不多了。。。
ARM汇编中,常有两种跳转方法:b 跳转指令、ldr 指令向PC 赋值。
我自己经过归纳如下:
(1) b step1 :b 跳转指令是相对跳转,依赖当前PC 的值,偏移量是通过该指令本身的bit[2
3:0]算出来的,这使得使用b 指令的程序不依赖于要跳到的代码的位置,只看指令本身。
(2) ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给PC,同样依
赖当前PC 的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址),所以可
以用它实现从Flash 到RAM 的程序跳转。
(3) 此外,有必要回味一下adr 伪指令,U-boot 中那段relocate 代码就是通过adr 实现当前
程序是在RAM 中还是flash 中。仍然用我当时的注释:
relocate: /* 把U-Boot 重新定位到RAM */
adr r0, _start /* r0 是代码的当前位置 */
/* adr 伪指令,汇编器自动通过当前PC 的值算出 如果执行到_start 时PC 的值,
放到r0 中:
当此段在flash 中执行时r0 = _start = 0;当此段在RAM中执行时_start = _TEX
T_BASE(在board/smdk2410/config.mk 中指定的值为0x33F80000,即u-boot 在
把代码拷贝到RAM 中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash 启动,还是RAM */
/* 此句执行的结果r1 始终是0x33FF80000,因为此值是又编译器指定的(ads 中设
置,或-D 设置编译器参数) */
cmp r0, r1 /* 比较r0 和r1,调试的时候不要执行重定位 */
下面,结合u-boot.lds 看看一个正式的连接脚本文件。这个文件的基本功能还能看明白,
虽然上面分析了好多,但其中那些GNU 风格的符号还是着实让我感到迷惑,好菜啊,怪不
得连被3 家公司鄙视,自己鄙视自己。。。
OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlea
rm")
;指定输出可执行文件是elf 格式,32 位ARM 指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000 ; 从0x0 位置开始
. = ALIGN(4) ; 代码以4 字节对齐
.text : ;指定代码段
{
cpu/arm920t/start.o (.text) ; 代码的第一个代码部分
*(.text) ;其它代码部分
}
. = ALIGN(4)
.rodata : { *(.rodata) } ;指定只读数据段
. = ALIGN(4);
.data : { *(.data) } ;指定读/写数据段
. = ALIGN(4);
.got : { *(.got) } ;指定got 段, got 段式是uboot 自定义的一个段, 非标准段
__u_boot_cmd_start = . ;把__u_boot_cmd_start 赋值为当前位置, 即起始位

.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd 段, uboot 把所有的u
boot 命令放在该段.
__u_boot_cmd_end = .;把__u_boot_cmd_end 赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start = .; 把__bss_start 赋值为当前位置,即bss 段的开始位置
.bss : { *(.bss) }; 指定bss 段
_end = .; 把_end 赋值为当前位置,即bss 段的结束位置
}
分享一篇我总结的uboot 学习笔记(转)
1. 下面代码是系统启动后U-boot 上电后运行的第一段代码,他是什么意思?
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl 16,0xdeadbeef
他们是系统定义的异常,一上电程序跳转到reset 异常处执行相应的汇编指令,下面定义出
的都是不同的异常,比如软件发生软中断时,CPU 就会去执行软中断的指令,这些异常中断
在CUP 中地址是从0 开始,每个异常占4 个字节。
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
操作系统先注册一个总的中断,然后去查是由哪个中断源产生的中断,再去查用户注册的中
断表,查出来后就去执行用户定义的用户中断处理函数。
ldr pc, _undefined_instruction 表示把_undefined_instruction 存放的数值存放到pc 指针上,
_undefined_instruction: .word undefined_instruction 表示未定义的这个异常是由.word 来定
义的,它表示定义一个字,一个32 位的数,.word 后面的数表示把该标识的编译地址写入
当前地址,标识是不占用任何指令的。把标识存放的数值copy 到指针pc 上面,那么标识上
存放的值是什么?是由.word undefined_instruction 来指定的,pc 就代表你运行代码的地址,
她就实现了CPU 要做一次跳转时的工作。
什么是编译地址?什么是运行地址?
32 位的处理器,它的每一条指令是4 个字节,以4 个字节存储顺序,进行顺序执行,CPU
是顺序执行的,只要没发生什么跳转,它会顺序进行执行,编译器会对每一条指令分配一个
编译地址,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。
运行地址是指,程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,
哪里就是运行的地址。比如有一个指令的编译地址是0x5,实际运行的地址是0x200,如果
用户将指令烧到0x200 上,那么这条指令的运行地址就是0x200,当编译地址和运行地址不
同的时候会出现什么结果?结果是不能跳转,编译后会产生跳转地址,如果实际地址和编译
后产生的地址不相等,那么就不能跳转。C 语言编译地址都希望把编译地址和实际运行地址
放在一起的,但是汇编代码因为不需要做C 语言到汇编的转换,可以人为的去写地址,所以
直接写的就是他的运行地址,这就是为什么任何bootloader 刚开始会有一段汇编代码,因为
起始代码编译地址和实际地址不相等,这段代码和汇编无关,跳转用的运行地址。编译地址
和运行地址如何来算呢?假如有两个编译地址a=0x10,b=0x7,b 的运行地址是0x300,那
么a 的运行地址就是b 的运行地址加上两者编译地址的差值,a-b=0x10-0x7=0x3,a 的运行
地址就是0x300+0x3=0x303。
假设uboot 上两条指令的编译地址为a=0x33000007 和b=0x33000001,这两条指令都落在
bank6 上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个是由用户烧
录进去的,假设运行地址的首地址是0x0,则a 的运行地址
为0x7,b 为0x1,就是这样算出来的。
为什么要分配编译地址?这样做有什么好处,有什么作用?
比如在函数a 中定义了函数b,当执行到函数b 时要进行指令跳转,要跳转到b 函数所对应
的起始地址上去,编译时,编译器给每条指令都分配了编译地址,如果编译器已经给分配了
地址就可以直接进行跳转,查找b 函数跳转指令所对应的表,进行直接跳转,因为有个编译
地址和指令对应的一个表,如果没有分配,编译器就查找不到这个跳转地址,要进行计算,
非常麻烦。
什么是相对地址?
以NOR Flash 为例,NOR Falsh 是映射到bank0 上面,SDRAM 是映射到bank6 上面,uboot
和内核最终是在SDRAM 上面运行,最开始我们是从Nor Flash 的零地址开始往后烧录,uboot
中至少有一段代码编译地址和运行地址是不一样的,编译uboot 或内核时,都会将编译地址
放入到SDRAM 中,他们最终都会在SDRAM 中执行,刚开始uboot 在Nor Flash 中运行,运
行地址是一个低端地址,是bank0 中的一个地址,但编译地址是bank6 中的地址,这样就会
导致绝对跳转指令执行的失败,所以就引出了相对地址的概念。那么什么是相对地址呢?至
少在bank0 中uboot 这段代码要知道不能用b+编译地址这样的方法去跳转指令,因为这段
代码的编译地址和运行地址不一样,那如何去做呢?要去计算这个指令运行的真实地址,计
算出来后再做跳转,应该是b+运行地址,不能出现b+编译地址,而是b+运行地址,而运行
地址是算出来的。
_TEXT_BASE:
.word TEXT_BASE //0x33F80000,在board/config.mk 中
这段话表示,用户告诉编译器编译地址的起始地址
Uboot 启动流程
1. 设置CPU 的启动模式
reset:
//设置CPU 进入管理模式 即设置相应的CPSR 程序状态字
/* * set the cpu to SVC32 mode*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
2. 关闭看门狗,关闭中断,所谓的喂狗是每隔一段时间给某个寄存器置位而已,在实际
中会专门启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗,停止喂狗
之后,cpu 会自动复位,一般都在外部专门有一个看门狗,做一个外部的电路,不在cpu 内
部使用看门狗,cpu 内部的看门狗是复位的cpu,当开发板很复杂时,有好几个cpu 时,就
不能完全让板子复位,但我们通常都让整个板子复位。看门狗每隔短时间就会喂狗,问题是
在两次喂狗之间的时间间隔内,运行的代码的时间是否够用,两次喂狗之间的代码是否在两
次喂狗的时间延迟之内,如果在延迟之外的话,代码还没改完就又进行喂狗,代码永远也改
不完。
//关闭看门狗的实际代码
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON //将pwtcon 寄存器地址赋给R0
mov r1, #0x0 //r1 的内容为0
str r1, [r0] //将R1 的内容送到Ro 寄存器中去
3. 屏蔽所有中断,为什么要关中断?中断处理中ldr pc 是将代码的编译地址放在了指针上,
而这段时间还没有搬移代码,所以编译地址上面没有这个代码,如果进行跳转就会跳转到空
指针上面
/* * mask all IRQs by setting all bits in the INTMR - default*/
mov r1, #0xffffffff //寄存器中的值全为11111111111111111111111111111111
ldr r0, =INTMSK //将管理中断的寄存器地址赋给ro
str r1, [r0] //将全1 的值赋给ro 地址中的内容
#if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
#endif
3. 设置时钟分频,为什么要设置时钟?起始可以不设,系统能不能跑起来和频率没有任
何关系,频率的设置是要让外围的设备能承受所设置的频率,如果频率过高则会导致cpu
操作外围设备失败
//设置CPU 的频率
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
4. 做bank 的设置
cpu_init_crit:
/* flush v4 I/D caches,关闭catch*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB *///协处理器
//禁止MMU
/** disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0 //关闭
为什么要关闭catch 和MMU 呢?catch 和MMU 是做什么用的?
Catch 是cpu 内部的一个2 级缓存,她的作用是将常用的数据和指令放在cpu 内部,MMU
是用来做虚实地址转换用的,我们的目的是设置控制的寄存器,寄存器都是实地址,如果既
要开启MMU 又要做虚实地址转换的话,中间还多一步,
先要把实地址转换成虚地址,然后再做设置,但对uboot 而言就是起到一个简单的初始化的
作用和引导操作系统,如果开启MMU 的话,很麻烦,也没必要,所以关闭MMU.
说道catch 就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到,他的本质
是告诉编译器不要对我的代码进行优化,优化的过程是将常用的代码取出来放到catch 中,
它没有从实际的物理地址去取,它直接从cpu 的缓存中去取,但常用的代码就是为了感知一
些常用变量的变化,如果正在取数据的时候发生跳变,那么就感觉不到变量的变化了,所以
在这种情况下要用Volatile 关键字告诉编译器不要做优化,每次从实际的物理地址中去取指
令,这就是为什么关闭catch 关闭MMU。但在C 语言中是不会关闭catch 和MMU 的,会打
开,如果编写者要感知外界变化,或变化太快,从catch 中取数据会有误差,就加一个关键
字Volatile。
5. bl lowlevel_init 下来初始化各个bank,把各个bank 设置必须搞清楚,对以后移植复杂
的uboot 有很大帮助
6.设置完毕后拷贝uboot 代码到4k 空间,拷贝完毕后执行内存中的uboot 代码
以上流程基本上适用于所有的bootloader,这就是step1 阶段
7. 问题:如果换一块开发板有可能改哪些东西?
首先,cpu 的运行模式,如果需要对cpu 进行设置那就设置,管看门狗,关中断不用改,
时钟有可能要改,如果能正常使用则不用改,关闭catch 和MMU 不用改,设置bank 有可能
要改。最后一步拷贝时看地址会不会变,如果变化也要改,执行内存中代码,地址有可能要
改。
8. Nor Flash 和Nand Flash 本质区别就在于是否进行代码拷贝,也就是下面代码所表述:无论
是Nor Flash 还是Nand Flash,核心思想就是将uboot 代码搬运到内存中去运行,但是没有拷
贝bss 后面这段代码,只拷贝bss 前面的代码,bss 代码是放置全局变量的。Bss 段代码是为
了清零,拷贝过去再清零重复操作
//uboot 代码搬运到RAM 中去
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //flash 中armboot_start 的起始地址
ldr r3, _bss_start //uboot_bss 的起始地址
sub r2, r3, r2 /* r2 <- size of armboot//uboot 实际程序代码的大小 */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
9. 看一下uboot.lds文件,在board/smdk2410目录下面,uboot.lds是告诉编译器这些段
改怎么划分,GUN编译过的段,最基本的三个段是RO,RW,ZI,RO表示只读,对应于具体
的指代码段,RW是数据段,ZI是归零段,就是全局变量的那段。Uboot代码这么多,如何
保证start.s会第一个执行,编译在最开始呢?就是通过uboot.lds链接文件进行
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; //起始地址
. = ALIGN(4); //4字节对齐
.text : //test指代码段,上面3行标识是不占用任何空间的
{
cpu/arm920t/start.o (.text) //这里把start.o放在第一位就表示把start.s

译时放到最开始,这就是为什么把uboot烧到起始地址上它肯定运行的是start.s
*(.text)
}
. = ALIGN(4); //前面的 “.” 代表当前值,是计算一个当前的值,是计算上
面占用的整个空间,再加一个单元就表示它现在的位置
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .; //bss表示归零段
.bss : { *(.bss) }
_end = .;
回忆一下GUN在编译代码时的四个步骤,1.预处理,2.编译,3.汇编,4.链接,链接就做的
是这个文件的动作,就是将这些文件重新map一下分配地址。
最后运行的是_start_armboot: .word start_armboot函数跳转到step2的阶段,这个
函数是uboot中第一个C代码,也是第一个在内存中运行的代码
U-BOOT 内存布局及启动过程浅析
本文以ARC600平台的某一实现为例,对U-BOOT的内存布局和启动方式进行简要的分析。
【内存布局】
在ARC600平台,U-BOOT的内存布局图1所示。
该布局由board/arc600/u-boot.lds文件定义,在链接的时候生成相应的二进制映像。首先,
定义起始地址为0x40800000,接下来是中断向量表,大小为256字节,按每个中断向量占
用4个字节的跳转地址算,最多可以有64个中断向量;第二部分是一些基础性的代码段,
它为下一步加载 boot或者kernel做准备,其大小为0x1700字节;第三部分是代码段的后
半部分,代码段的大部分代码都在这里;第四部分只读数据区;第五部分为可读写数据区;
第六部分为U-BOOT命令代码区;最后一部分为未初始化数据段。
有一点比较疑惑的就是U-BOOT命令代码区存放的分明是代码,但它却在数据段。内核中会
把一些初始化代码放在数据区,因为这些代码只运行一次,放在数据区可以在内核启动后回
收该区域内存。但显然U-BOOT命令不可能只运行一次,为何要把它放在数据段?不解!
【启动过程】
众所周知,U-BOOT是存放在FLASH上的。系统启动时,CPU会映射FLASH到它的内存空间(映
射一部分、还是全部FLASH空间?),然后执行 FLASH上的代码。首先,进入
cpu/arc600/start.S中的入口_start,进行内存初始化,接着把U-BOOT的前0x1800字节
从 FLASH复制到内存的0x40800000处,也就是链接时的地址;然后对bss段进行清零,设
置堆栈指针,为运行C函数做准备;下一步,运行C函数检测在规定时间内是否有按键发生,
如有则加载boot的后半部分(0x40801800——DATA_END)并启动boot,无则加载kernel并
启动 kernel。U-BOOT启动的前半部分流程如图2所示。
U-BOOT启动的后半部分,会进行heap、环境变量(env)的初始化,PHY驱动的加载等工作,
然后进入一个无限循环开始shell的运行,shell运行过程中的内存示意如图3所示。其中,
heap和stack依次排列在bss段的后面,图中所示的free area则为U-BOOT未用到的内存。
图 3中,heap区域为malloc()提供内存。在uClib库中,malloc()是通过sbrk()或者mmap()
实现的,而sbrk()和 mmap()是在内核中实现的。U-BOOT作为系统最早运行的程序,没有内
核的支持。为了实现malloc(),它定义一个32K的heap区域,在此区域的基础上实现了简
化版的sbrk()。
图3中,stack区域是在U-BOOT启动的前半部分中第三步设置的。它首先根据BSS_END、heap
大小和stack大小算出stack_bottom的值,然后设置堆栈指针SP和帧指针FP为
stack_bottom - 4。
u-boot 中的命令实现
软件平台:u-boot-1.1.6,gcc for blackfin,visual dsp 5.0
我们知道,u-boot的运行过程是首先进行一些初始化化工作,然后在一个死
循环中不断接收串口的命令并进行解释执行,下面我们就看看执行部分代码的实
现,见common/main.c中的run_command:
int run_command (const char *cmd, int flag)
{

while (*str) {

/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
/* found - check max args */
if (argc > cmdtp->maxargs) {
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}

/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc ())
return 0; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
很简单的一个过程,扩展宏定义 -> 分析命令及其参数 -> 查找命令 -> 执
行命令,有意思的地方在查找命令上(common/command.c):
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = 0;
/*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
for (cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}
return NULL; /* not found or ambiguous command */
}
看起来还是很简单的一个过程,在一个命令数组中查找是否有指定名称的命
令。问题是,在这里使用的两个符号__u_boot_cmd_start和__u_boot_cmd_end,
在所有的C文件中都找不到它们的定义,那么它们的空间从哪里来呢?这些分散
在不同文件中的结构体又是如何能够放在同一个数组中呢?
答案就在board/bf561-ezkit/u-boot.lds.s中,这个文件其实就是一个链
接文件,类似于VDSP中的LDF文件,see see:
___u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
___u_boot_cmd_end = .;
这几句话的意思其实就是指示链接器将所有.u_boot_cmd数据段中的内容全
部放在一起,而且___u_boot_cmd_start和___u_boot_cmd_end是不会占用任何
存储空间的,它们只是用来指示地址的两个符号而已。那么数据段的定义在哪里
呢?看看U_BOOT_CMD的宏定义吧(include/command.h):
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
__attribute__ ((unused,section (".u_boot_cmd")))就指示编译器将这些用U_BOOT_CMD定
义的结构体放在.u_boot_cmd这个数据段中。
如果要在VDSP中编译u-boot,那么就需要在LDF文件中也定义这样一个数
据段:
.u_boot_cmd
{
___u_boot_cmd_start = .;
INPUT_SECTIONS(common.dlb(.u_boot_cmd) common.dlb(__u_boot_cmd))
___u_boot_cmd_end = .;
} > MEM_SDRAM_U_BOOT
不过让人郁闷的是:如果在一个定义命令的C文件中没有一个函数被其它文
件引用,VDSP在链接时将认为这是一个多余的文件,从而不会将这个文件中的
函数链接进来,当然就无法使用其中定义的这些命令,如cmd_load.c。
解决的办法可以是在这些文件中添加一个空函数,并在主函数中调用它们,
这样VDSP就会把这个文件链接进来了。
U-BOOT 环境变量实现
(基于smdk2410)
1.相关文件
common/env_common.c
供u-boot 调用的通用函数接口,它们隐藏了env 的不同实现方式,比如dataflash, epprom, flash 等
common/env_dataflash.c
env 存储在dataflash 中的实现
common/env_epprom.c
env 存储在epprom 中的实现
common/env_flash.c
env 存储在flash 中的实现
common/env_nand.c
env 存储在nand 中的实现
common/env_nvedit.c
实现u-boot 对环境变量的操作命令
environment.c
环境变量以及一些宏定义
env 如果存储在Flash 中还需要Flash 的支持。
2.数据结构
env 在 u-boot 中通常有两种存在方式,在永久性存储介质中( Flash NVRAM 等 )在SDRAM,可以
配置不使用 env 的永久存储方式,但这不常用。u-boot 在启动的时候会将存储在永久性存储介质中的
env 重新定位到 RAM 中,这样可以快速访问,同时可以通过saveenv 将 RAM 中的 env 保存到永久
性存储介质中。
在include/environment.h 中定义了表示env 的数据结构
typedef struct environment_s
{
unsigned long crc; /* CRC32 over data bytes */
#ifdef CFG_REDUNDAND_ENVIRONMENT
unsigned char flags; /* active/obsolete flags */
#endif
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
关于以上结构的说明:
crc 是u-boot 在保存env 的时候加上去的校验头,在第一次启动时一般 crc 校验会出错,这很正常,因
为这时 Flash 中的数据无效。
data 字段保存实际的环境变量。u-boot 的 env 按 name=value”\0”的方式存储,在所有env 的最后
以”\0\0”表示整个 env 的结束。新的name=value 对总是被添加到 env 数据块的末尾,当删除一个
name=value 对时,后面的环境变量将前移,对一个已经存在的环境变量的修改实际上先删除再插入。
env 可以保存在 u-boot 的 TEXT 段中,这样 env 就可以同 u-boot 一同加载入RAM 中,这种方法
没有测试过。
上文提到u-boot 会将 env 从 flash 等存储设备重定位到 RAM 中,在 env 的不同实现版本
( env_xxx.c )中定义了 env_ptr, 它指向 env 在RAM 中的位置。u-boot 在重定位 env 后对环境
变量的操作都是针对 env_ptr。
env_t 中除了数据之外还包含校验头,u-boot 把env_t 的数据指针有保存在了另外一个地方,这就
是 gd_t 结构( 不同平台有不同的 gd_t 结构 ),这里以ARM 为例仅列出和 env 相关的部分
typedef struct global_data
{

unsigned long env_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct ??? */
unsigned long env_valid /* Checksum of Environment valid */

} gd_t;

gd_t.env_addr 即指向 env_ptr->data。
3.ENV 的初始化
start_armboot : ( lib_arm/board.c )
*env_init : env_xxx.c( xxx = nand | flash | epprom … )
env_relocate : env_common.c
*env_relocate_spec : env_xxx.c( xxx=nand | flash | eporom… )
3.1env_init
实现 env 的第一次初始化,对于nand env (非embedded 方式):
Env_nand.c : env_init
gd->env_addr = (ulong)&default_environment[0]; //先使gd->env_addr 指向默认的环境变量
gd->env_valid = 1;// env 有效位置1
3.2 env_relocate
#ifdefine ENV_IS_EMBEDDED
…(略)
#else
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
#endif
if( gd->env_valid == 0) // 在 Env_annd.c : env_init 中已经将 gd->env_valid 置1
{

}
else
env_relocate_spec ();// 调用具体的 env_relocate_spec 函数
gd->env_addr = (ulong)&(env_ptr->data);// 最终完成将环境变量搬移到内存
这里涉及到两个和环境变量有关的宏
ENV_IS_EMBEDDED : env 是否存在于 u-boot TEXT 段中
CFG_ENV_SIZE : env 块的大小
实际上还需要几个宏来控制u-boot 对环境变量的处理
CFG_ENV_IS_IN_NAND : env 块是否存在于Nand Flash 中
CFG_ENV_OFFSET : env 块在 Flash 中偏移地址
3.3*env_relocate_spec
这里仅分析 Nand Flash 的 env_relocate_spec 实现
如果未设置 CFG_ENV_OFFSET_REDUND,env_relocate_spec 的实现如下 :
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
ulong total;
int ret;
total = CFG_ENV_SIZE;
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return use_default();
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
上面的代码很清楚的表明了 env_relocate_spec 的意图, 调用 nand_read 将环境变量从
CFG_ENV_OFFSET 处读出,环境变量的大小为 CFG_ENV_SIZE 注意 CFG_ENV_OFFSET 和
CFG_ENV_SIZE 要和 Nand Flash 的块/页边界对齐。读出数据后再调用crc32 对env_ptr->data 进
行校验并与保存在 env_ptr->crc 的校验码对比,看数据是否出错,从这里也可以看出在系统第一次启动
时,Nand Flash 里面没有存储任何环境变量,crc 校验肯定回出错,当我们保存环境变量后,接下来再启
动板子u-boot 就不会再报crc32 出错了。
4. ENV 的保存
由上问的论述得知, env 将从永久性存储介质中搬到RAM 里面,以后对env 的操作,比如修改环境变量
的值,删除环境变量的值都是对这个 env 在RAM 中的拷贝进行操作,由于RAM 的特性,下次启动时所
做的修改将全部消失,u-boot 提供了将env 写回 永久性存储介质的命令支持 : saveenv,不同版本的
env ( nand flash, flash … ) 实现方式不同, 以Nand Flash 的实现( 未定义
CFG_ENV_OFFSET_REDUND)为例
Env_nand.c : saveenv
int saveenv(void)
{
ulong total;
int ret = 0;
puts ("Erasing Nand...");
if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
return 1;
puts ("Writing to Nand... ");
total = CFG_ENV_SIZE;
ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
if (ret || total != CFG_ENV_SIZE)
return 1;
puts ("done\n");
return ret;
}
Nand Flash 的 saveenv 命令实现很简单,调用nand_erase 和nand_write 进行Nand Flash 的
erase, write。nand_write/erase 使用的是u-boot 的nand 驱动框架,我在做开发的过程中使用的是
nand_legacy 驱动, 所以可以把nand_erase 和nand_write 改成nand_legacy_erase 和
nand_legacy_rw 就可实现nand_legacy 驱动的保存环境变量版本。
========================================================
================
U-Boot 环境变量
U-Boot 通过环境变量(env)为用户提供一定程度的可配置性,这些环境变量包括串口终端所使用的波特
率(baudrate)、启动操作系统内核的参数(bootargs)、本地IP 地址(ipaddr)、网卡MAC 地址(ethaddr)
等等。环境变量可以固化到非易失性存储介质中,使用printenv / saveenv 命令来查看和修改。本例中,
环境变量固化到Flash 中(AM29LV160DB,2MB)。
可配置性意味着环境变量中的项目是可以被添加、删除和修改的,即环境变量的内容可能会频繁变化。为
了不让这种变化对U-Boot 的代码和数据造成破坏,通常的选择是在Flash 中准备一个专用的sector 来
存储环境变量。简化的ROM 分配模型如下图所示,monitor 占用 Flash 前256KB,env 置于其后,Flash
的最后一部分用来存放压缩的操作系统内核。
AM29LV160DB 分为35 个sector,地址范围分配如下:
Sector Size ( KB ) Address Range ( Hex )
SA0 16 000000 ~ 003FFF
SA1 8 004000 ~ 005FFF
SA2 8 006000 ~ 007FFF
SA3 16 008000 ~ 00FFFF
SA4 32 010000 ~ 01FFFF
SA5 64 020000 ~ 02FFFF
... 64 ...
SA34 64 1F0000 ~ 1FFFFF
由于U-Boot 代码通常达到100KB 左右,且必须从地址0 处开始,按照这样的分配方式,我们将不得不
为env 分配一块64KB 的sector,而实际中使用到的可能只是其中的几百字节!U-Boot 还会为env 在
RAM 中保持一块同样大小的空间,这就造成ROM 和RAM 空间不必要的浪费。
为了尽可能地减少资源浪费,同时保证系统的健壮性,我们可以把env 放置在Flash 中容量最小的sector
里。这样,env 嵌入(embed)到U-Boot 的代码段。在common/environment.h 中会比较env 和
monitor 的范围,如果确定env 位于monitor 内,则定义ENV_IS_EMBEDDED。
# if (CFG_ENV_ADDR >= CFG_MONITOR_BASE) && \
(CFG_ENV_ADDR+CFG_ENV_SIZE) <= (CFG_MONITOR_BASE + CFG_MONITOR_LEN)
# define ENV_IS_EMBEDDED 1
# endif
修改board/buf/EVB44B0/u-boot.lds:
/*------------------------------------------------------------
* Environment Variable setup
*/
#define CFG_ENV_IS_IN_FLASH 1 /* 使用Flash 存储env */
#define CFG_ENV_SIZE 0x2000 /* 容量8KB (SA1) */
#define CFG_ENV_OFFSET 0x4000 /* 偏移地址 (SA1) */
$(LD)将一系列的obj 文件连接成elf 格式文件,其输出文件的内存布局由linker script 决定。修改
board/buf/EVB44B0/u-boot.lds:
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c44b0/start.o (.text)
board/buf/EVB44B0/lowlevel_init.o (.text)
lib_generic/string.o (.text)
lib_generic/zlib.o (.text)
. = env_offset;
common/environment.o (.text)
*(.text)
}
/* other sections ... */
}
从u-boot.map 选择那些U-Boot 运行必须的,且不易受CFG_*宏影响的obj 文件,填充到start.o 后
面。可以参考board/trab/u-boot.lds。
env_offset 定义在common/environment.c 中:
#define GEN_SYMNAME(str) SYM_CHAR #str
#define GEN_VALUE(str) #str
#define GEN_ABS(name, value) \
asm (".globl " GEN_SYMNAME(name)); \
asm (GEN_SYMNAME(name) " = " GEN_VALUE(value))
GEN_ABS(env_offset, CFG_ENV_OFFSET);
u-boot 代码链接的问题
环境和配置:u-boot-1.1.2, arm-linux-gcc(v3.2), redhat linux9.0,
cpu(s3c44b0), board(B2)
在/board/dave/B2/u-boot.lds有
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c44b0/start.o (.text)
*(.text)
}
而/board/dave/B2/config.mk中有
TEXT_BASE = 0x0C100000
最后编译出来的代码反汇编得到
0c100000 <_start>:
c100000: ea00000a b c100030
c100004: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100008: e28ff303 add pc, pc, #201326592 ; 0xc000000
c10000c: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100010: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100014: e28ff303 add pc, pc, #201326592 ; 0xc000000
c100018: e28ff303 add pc, pc, #201326592 ; 0xc000000
c10001c: e28ff303 add pc, pc, #201326592 ; 0xc000000
0c100020 <_TEXT_BASE>:
c100020: 0c100000 ldceq 0, cr0, [r0]
0c100024 <_armboot_start>:
c100024: 0c100000 ldceq 0, cr0, [r0]
0c100028 <_bss_start>:
c100028: 0c115694 ldceq 6, cr5, [r1], -#592
0c10002c <_bss_end>:
c10002c: 0c1198a4 ldceq 8, cr9, [r1], -#656
0c100030 :
c100030: e10f0000 mrs r0, CPSR
链接得到的起始地址为什么是TEXT_BASE,而不是0呢,所以现在只能够下载到
ram中运行,但是无法烧写道flash中跑,这是怎么回事呢?u-boot中应该是
start.S中的这段代码在flash中运行吧,后面就把自身拷贝到ram中TEXT_BASE
地址处,为什么在链接文件中指定的_start的起始地址为0x00000000呢?
blob中把整个代码分为两部分,所以有两个连接文件,前面1k在flash中运行,
链接起始地址为0x0,ram中运行的,然后通过工具把两部分组合到一起,但是
u-boot是怎么做的呢?
迷惑中,请帮忙解答,谢先!
Re: u-boot代码链接的问题
好象与这个地址没关系的
u-boot既可以在SDRAM中,
也可在Flash中运行
Re: u-boot代码链接的问题
兄弟,首先需要知道CPU的启动方式,一般来说有BOOTROM,SPI,FLASH这三种
方式;一般来说也不会存在从SDRAM/DRAM上启动机器,因为它们不可能作为永久
存储。
然后就是需要知道,外设地址映射关系,对于CPU来说总是有统一的IO地址映射,
并且在不同的访问模式下,地址映射关系不一定相同。
还有,代码可以运行在不同的介质上,如:上述三种再包括RAM/ROM。
另外:还要知道代码段的链接地址,这个你应该是知道的。
最后,就可以考虑你的FLASH启动问题了。在Uboot里面的-text的参数,应该是
真对某个硬件系统的配置,这个就是FLASH的高端地址或者低端地址。
一般来说,bootloader的代码的第一段总是运行在永久介质上,例如:FLASH,然
后才将代码搬移到DRAM中,这个时候在dram中执行第二段BOOTLOADER的代码段,
也就是你说下载到内存中的说法,实际上他都已经运行了一个阶段了。在内存中
的运行入口地址,按照系统情况可以自行安排了。
因此,这个最开始的FLASH地址,肯定不是0,是什么看看你的单板的DATASHEET
和编程参考
Re: u-boot代码链接的问题
谢谢,我的板子cpu是s3c44b0,不支持memory remap,flash为4M,从0x00000000
到0x00400000,
sdram为8M,地址从0x0c000000到0x0c800000,我不清楚的问题时链接脚本中指
定的_start的入口地址为0x00000000,为何编译出来的代码。链接地址是从
0x0c100000开始的,即把TEXT_BASE作为_start的入口地址了,如果我把
TEXT_BASE改为0x00000000,那么后面的rellocate代码还有意义吗?
Re: u-boot代码链接的问题
我猜测,问题可能有可能,我说的仅仅是may be:
和我前面说的一样,boot loader一般有两段代码段(独立的),在连接的时候也
是分别连接,故此有两个代码连接文件,和编译过程共产生的两个MAP文件,可
以查一下你看的连接文件和编译文件是不是对应的。
另外有两个建议:
1:这个也是疑惑,我看了你的问题后,作了这样的前提假设(个人的理解):在
dram里的loader stage起始地址是定义在sdram的最低端。从这个假设出来,我
觉得这个链接也是有问题的,因为一般来说sdram的最低端是做向量表、和模式
栈顶区、参数区来用的,这个肯定有问题
2:分析这个问题,首先要确定你的编译过程,可以把编译过程定向到一个文件中
仔细的分析,可以看到编译链接各个细节。不妨发给我一份
[email protected]
Re: u-boot代码链接的问题
TEXTADDR 是内核的虚拟起始地址,并且在arch// 下的Makefile 中指
定它的值。这个地址必须与引导装载程序使用的地址相匹配。 一旦引导装载程序
将内核复制到闪存或DRAM 中,内核就被重新定位到TEXTADDR — 它通常在DRAM
中,然后,引导装载程序将控制转给这个地址,以便内核能开始执行。
Re: u-boot代码链接的问题
我已经理解了:
“链接得到的起始地址为什么是TEXT_BASE,而不是0呢,”
因为u_boot如果从flash运行的话,那么它会将自己的代码拷贝到RAM中,然后
运行。u-boot开始部分代码与编译的入口没有关系,而主要的代码是在RAM中运
行,因此编译的入口地址是TEXT_BASE.因此u-boot既可以flash运行,也可以
ram运行。
“为什么在链接文件中指定的_start的起始地址为0x00000000呢?”
lds文件中的起始地址为0x00000000是不起作用的,由-TTEXT_BASE参数替代的。
刚开始比较疑惑的原因是对:
126 relocate: /* relocate U-Boot to RAM */
127 adr r0, _start /* r0 <- current position of code */
adr这条指令没有理解正确,因为把它想成mv r0,_start了,实际上adr这里的
_start是相对的,如果从flash运行的话,r0就是0, 如果从ram运行的话,r0
就是C100000。
我现在可以运行u-boot了,串口可以显示内容并且可以使用命令。但网卡驱动和
flash驱动还有问题。慢慢搞就可以搞定,因为可以用printf调试的。
Re: u-boot代码链接的问题
楼上是不是说 因为adr指令是小范围地址读取指令,所以在不同的运行环境下,
_start的值不同啊。
针对44box中的代码
adr r0, real_vectors
add r2, r0, #1024
ldr r1, =0x0c000000
add r1, r1, #0x08
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop
复制向量中断,为什么要复制1024个字节呢?
问得比较弱,见笑了。
Re: u-boot代码链接的问题
多一点是没有关系的
Re: u-boot代码链接的问题
在FLASH中以相对地址运行,然后一个绝对跳转完成从FLASH到RAM的切换,应
该制定为UBOOT在RAM中的起始地址!
看makefile 文件
$(obj)u-boot.img: $(obj)u-boot.bin
./tools/mkimage -A $(ARCH) -T firmware -C none \
-a $(TEXT_BASE) -e 0 \
-n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
sed -e 's/"[ ]*$$/ for $(BOARD) board"/') \
-d $< $@
ldr 和adr 在使用标号表达式作为操作数的
区别
ARM汇编有ldr指令以及ldr、adr伪指令,他门都可以将标号表达式作为操作
数,下面通过分析一段代码以及对应的反汇编结果来说明它们的区别。
ldr r0, _start
adr r0, _start
ldr r0, =_start
_start:
b _start
编译的时候设置 RO 为 0x30000000,下面是反汇编的结果:
0x00000000: e59f0004 ldr r0, [pc, #4] ; 0xc
0x00000004: e28f0000 add r0, pc, #0 ; 0x0
0x00000008: e59f0000 ldr r0, [pc, #0] ; 0x10
0x0000000c: eafffffe b 0xc
0x00000010: 3000000c andcc r0, r0, ip
1.ldr r0, _start
这是一条指令,从内存地址 _start 的位置把值读入。
在这里_start是一个标号(是一个相对程序的表达式),汇编程序计算相对于 PC 的
偏移量,并生成相对于 PC的前索引的指令:ldr r0, [pc, #4]。执行指令后,r0 =
0xeafffffe。
ldr r0, _start是根据_start对当前PC的相对位置读取其所在地址的值,因此
可以在和_start标号的相对位置不变的情况下移动。
2.adr r0, _start
这是一条伪指令,总是会被汇编程序汇编为一个指令。汇编程序尝试产生单个 ADD
或 SUB 指令来装载该地址。如果不能在一个指令中构造该地址,则生成一个错误,并且汇
编失败。
在这里是取得标号_start 的地址到 r0,因为地址是相对程序的,因此ADR产生
依赖于位置的代码,在此例中被汇编成:add r0, pc, #0。因此该代码可以在和标号相对位
置不变的情况下移动;
假如这段代码在 0x30000000 运行,那么 adr r0, _start 得到 r0 = 0x3000000c;
如果在地址 0 运行,就是 0x0000000c 了。
通过这一点可以判断程序在什么地方运行。U-boot中那段relocate代码就是通过
adr实现当前程序是在RAM中还是flash中,下面进行简要分析。
relocate: /* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代码的当前位置 */
/* adr伪指令,汇编器自动通过当前PC的值算出 如果执行到_start时PC的值,放
到r0中:
当此段在flash中执行时r0 = _start = 0;当此段在RAM中执行时_start =
_TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x30000000,即u-boot在把代码
拷贝到RAM中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */
/* 此句执行的结果r1始终是0x30000000,因为此值是又编译器指定的(ads中设置,
或-D设置编译器参数) */
cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */
3.ldr r0, =_start
这是一条伪指令,是一个相对程序的或外部的表达式。汇编程序将相对程序的标
号表达式 label-expr 的值放在一个文字池中,并生成一个相对程序的 LDR 指令来从文字
池中装载该值,在此例中生成的指令为:ldr r0, [pc, #0],对应文字池中的地址以及值为:
0x00000010: 3000000c。如果 label-expr 是一个外部表达式,或者未包含于当前段内,则
汇编程序在目标文件中放置一个链接程序重定位命令。链接程序在链接时生成地址。
因此取得的是标号 _start 的绝对地址,这个绝对地址(运行地址)是在连接的
时候确定的。它要占用 2 个 32bit 的空间,一条是指令,另一条是文字池中存放_start 的
绝对地址。因此可以看出,不管这段代码将来在什么地方运行,它的结果都是 r0 =
0x3000000c。由于ldr r0, =_start取得的是_start的绝对地址,这句代码可以在_start
标号的绝对位置不变的情况下移动;如果使用寄存器pc在程序中可以实现绝对转移。
参考资料:
1. ARM DUI 0204BSC,RealView 编译工具 2.0 版 汇编程序指南,
http://infocenter.arm.com/help/index.jsp
2. GNU汇编使用经验, http://blog.chinaunix.net/u1/37614/showart_390095.html
3. 对.lds连接脚本文件的分析,
http://blog.chinaunix.net/u1/58780/showart.php?id=462971
start_armboot 浅析
ARM920t 架构的CPU 在完成基本的初始化后(ARM 汇编代码),就进入它的C 语言代
码,而C 语言代码的入口就是start_armboot, start_armboot 在lib_arm/board.c 中。start_armboot
将完成以下工作。
1.全局数据结构的初始化
比如gd_t 结构的初始化:
251 gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));
_armboot_start 是u-boot 在RAM 中的开始地址(对于u-boot 最终搬移到RAM 中运行的情
况),CFG_MALLOC_LEN 在include/configs/.h 中定义。
bd_t 结构的初始化:
272 gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));
u-boot 把bd_t 结构紧接着gd_t 结构存放。
内存分配的初始化
316 mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);
经过以上的初始化后,u-boot 在内存中的布局为(在底端为低地址)
-----------------------------
BSS
-----------------------------
U-BOOT TEXT/DATA
-----------------------------
CFG_MALLOC_LEN
-----------------------------
gd_t
-----------------------------
bd_t
-----------------------------
STACK
-----------------------------
2.调用通用初始化函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_sequence[]是init_fnc_t 函数指针数组,这个数组包含了众多初始化函数,比如cpu_init,
board_init 等。
//init_fnc_t *init_sequence[] = {
// cpu_init, /* basic cpu dependent setup */
// board_init, /* basic board dependent setup */
// interrupt_init, /* set up exceptions */
// env_init, /* initialize environment */
// init_baudrate, /* initialze baudrate settings */
// serial_init, /* serial communications setup */
// display_banner,
// dram_init, /* configure available RAM banks */
// display_dram_config,
// NULL,
// };
3.初始化具体设备
这一部分包括对Flash,LCD,网络的初始化等,例如
318 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
367 devices_init();
386 #ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
4.初始化环境变量
环境变量在通用初始化函数里面,已经初始化一次(env_init),这里调用env_relocate 对环
境变量进行重新定位。在我的另一篇文章”U-BOOT ENV 实现”中有对环境变量实现的讨论。
5.进入主循环
当然start_armboot 除了以上工作外,还完成其它的初始化工作,具体参考lib_arm/board.c,
在一切准备就绪之后,就进入u-boot 的主循环:
416 for (;;) {
main_loop ();
}
main_loop 的代码比较长,基本是就是执行用户的输入命令。
u-boot 编译过程
现在介绍一下u-boot 的编译过程,这里用的uboot 版本是U-Boot 2008.10,硬
件用smdk2410,这个板子用得比较普遍,uboot 已经有对其的支持。通过我们
对编译过程和代码的了解,我们也容易用uboot 支持我们自己需要的硬件。
编译命令非常简单:
make smdk2410_config (生成配置)
make all (生成最终文件)
当然,更好的做法是把编译出的文件生成到另外一个目录,并make clean 如:
export BUILD_DIR=../tmp
make distclean
make smdk2410_config
make all
现在,我们可以来看看Makefile,u-boot 的Makefile 文件非常大。但是,其结
构却并不复杂。
u-boot 已经支持了很多硬件,前半部分是共用部分,编译出最终的uboot 可执行
文件。
而后半部分,是为各种不同的硬件进行配置,每种硬件有一个目标,每个的做法
都非常类似,我们用到的是:
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
这里的 MKCONFIG := $(SRCTREE)/mkconfig
实际上是调用脚本mkconfig,而这个脚本做的工作简单如下:
建立include/config.mk 文件
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
echo "VENDOR = $5" >> config.mk
echo "SOC = $6" >> config.mk
建立include/config.h
echo "#include " >>config.h
在这里$1-$6 的值分别是:smdk2410 arm arm920t smdk2410 NULL s3c24x
0
而执行了 make smdk2410_config 之后,就生成了相应的config.mk,config.
h 两个文件。
在config.mk 文件中,定义了相应硬件信息 : ARCH CPU BOARD VENDO
R SOC
在config.h 文件中,包含了相应硬件的头文件smdk2410.h ,位于include\conf
igs 目录下。
如果新建自己的硬件项目,那么也需要建立相应的头文件在这个地方。
这样,uboot 的配置已经生成,下一次介绍make all 的过程。
接着上次,这次介绍make all 的过程。
首先,介绍一下生成的config.mk 和 config.h 如何使用,得到正确配置的。
config.mk 直接被include 到Makefile 来,并使用其定义如下:
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
这样可以直接选择需要编译的模块,例如:
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
config.h 被include/common.h 所包含,而它有包含了相应硬件的头文件。
common.h <--- config.h <--- smdk2410.h
除了在源程序中,使用这些头文件的定义之外,uboot 还有一个脚本通过解析c
ommon.h 以及其包含的所有头文件信息,来生成配置信息如下形式:
CONFIG_BAUDRATE=115200
CONFIG_NETMASK="255.255.255.0"
CONFIG_DRIVER_CS8900=y
CONFIG_ARM920T=y
CONFIG_RTC_S3C24X0=y
CONFIG_CMD_ELF=y
而头文件的定义的形式如下,对比可以看出脚本的工作原理。
#define CONFIG_BAUDRATE 115200
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_DRIVER_CS8900 1 /* we have a CS8900 on-bo
ard */
#define CONFIG_ARM920T 1 /* This is an ARM920T Core */
#define CONFIG_RTC_S3C24X0 1
#define CONFIG_CMD_ELF
通过这样,uboot 可以自动得到一个模块选择的配置功能。如果我们需要添加什
么定义或者功能,也可以在相应的头文件中加入定义实现。
现在,配置已经得到,就看最后的编译流程。
编译分为五大部分,分别如下:
1. $(SUBDIRS) 工具,例子等,包括目录:tools examples api_examples
2. $(OBJS) 启动模块 cpu/arm920t/start.o
3. $(LIBBOARD) 板子支持模块 board/smdk2410/libsmdk2410.a
4. $(LIBS) 其他模块,有诸如以下模块:
cpu/arm920t/libarm920t.a
cpu/arm920t/s3c24x0/libs3c24x0.a
lib_arm/libarm.a
fs/jffs2/libjffs2.a
fs/yaffs2/libyaffs2.a
net/libnet.a
disk/libdisk.a
drivers/bios_emulator/libatibiosemu.a
drivers/mtd/libmtd.a
drivers/net/libnet.a
drivers/net/phy/libphy.a
drivers/net/sk98lin/libsk98lin.a
drivers/pci/libpci.a
common/libcommon.a /
5. $(LDSCRIPT) 链接脚本
编译完成这五部分,链接成elf 格式的u-boot 文件,最后通过objcopy -O bina
ry 命令将elf 格式转换成为raw binary 格式的文件u-boot.bin 就可以烧到板子上
使用了。
mkconfig 文件的分析
http://niutao.org/blog/?p=50
在编译u-boot 之前都要执行”make XXX_config”命令,笼统的说是配置u-boot,使其编译出
适合目标板的bootloader。那么该命令都做了那些工作,具体的执行过程是怎样的?
我们首先从u-boot 的Makefile 文件看起,例如我们首先执行”make smd2410_config”
命令,则在Makefile 中会执行:
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
也就是:
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24×0
也就是说执行”make XXX_config”之后,实际上执行的是mkconfig 脚本,下面是对
mkconfig 文件的分析:
#!/bin/sh -e
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output
#如果命令行参数中有--,-a,-n 等参数,则执行以下循环。
#如果有-n XXX_config 或者-n XXX,则取出XXX 作为目标板的名字
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
#如果传递给该脚本的参数小于4 个或者大于6 个,则退出
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
#如果BOARD_NAME 为空,则BOARD_NAME 等于传递给该脚本的第一个参数
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
echo "Configuring for ${BOARD_NAME} board..."
#OBJTREE 和SRCTREE 都是在Makefile 中导出的变量,分别为编译目录和源码目录
#如果编译目录和源码目录不为同一目录,则执行一下命令,创建$(OBJTREE)/include 等目录
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
#如果编译目录和源码目录为同一个目录,则进入include 目录,删除旧的asm 链接,创建目
#标板到asm 的链接,例如如果目标体系结构为arm,则创建asm 软链接指向asm-arm。
cd ./include
rm -f asm
ln -s asm-$2 asm
fi
#删除目标平台下的旧的arch 链接
rm -f asm-$2/arch
#如果第6 个参数长度为0 或者其等于NULL,则创建arch-$3 到asm-$2 的链接,对于命令
#"./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0"
# $6=s3c24x0 不为空
# $3=arm920t $2=arm
#则最终执行的命令为"ln -s arch-s3c24x0 asm-arm/arch"
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
#如果目标平台为ARM,则删除建立的asm-arm 链接,重新建立从proc-armv 到asm-arm 的链接
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi
#
# Create include file for Make
#
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
#如果参数5 存在并且不为NULL,则将VENDOR = $5 追加在config.mk 文件中
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
#如果参数6 存在并且不为NULL,则将SOC = $6 追加在config.mk 文件中
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
#创建config.h 文件
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include " >>config.h
exit 0
总结一下,”make XXX_config”总共做了一下工作:
(1)确定开发板名称为BOARD_NAME = $1。
(2)创建一些链接文件,为编译u-boot 做准备:
ln -s asm-$2 asm
ln -s arch-$6 asm-$2/arch
ln -s proc-armv asm-$2/proc #仅在目标平台为arm 的时候才执行。
(3)创建顶层Makefile 包含的文件include/config.mk。
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $5
SOC = $6
(4)创建开发板相关的头文件include/config.h。
/* Automatically generated - do not edit */
#include
从NAND 闪存中启动U-BOOT 的设计
南昌大学信息工程学院 刘晔 汪灿 2007-02-12 21:29:20 电子设计应用/
引言
随着嵌入式系统的日趋复杂,它对大容量数据存储的需求越来越紧迫。而嵌入式设备低功耗、小体积以
及低成本的要求,使硬盘无法得到广泛的应用。NAND闪存设备就是为了满足这种需求而迅速发展起来的。
目前关于U-BOOT的移植解决方案主要面向的是微处理器中的NOR 闪存,如果能在微处理器上的NAND 闪存
中实现U-BOOT的启动,则会给实际应用带来极大的方便。
U-BOOT简介
U-BOOT 支持ARM、 PowerPC等多种架构的处理器,也支持Linux、NetBSD和VxWorks等多种操作系统,
主要用来开发嵌入式系统初始化代码bootloader。bootloader是芯片复位后进入操作系统之前执行的一段
代码,完成由硬件启动到操作系统启动的过渡,为运行操作系统提供基本的运行环境,如初始化CPU、堆
栈、初始化存储器系统等,其功能类似于PC机的BIOS。U-BOOT执行流程图如图1所示。
图1 U-BOOT启动流程图
NAND 闪存工作原理
S3C2410开发板的NAND闪存由NAND闪存控制器(集成在S3C2410 CPU中)和NAND闪存芯片(K9F1208U0A)
两大部分组成。当要访问NAND闪存芯片中的数据时,必须通过NAND闪存控制器发送命令才能完成。所以,
NAND闪存相当于S3C2410的一个外设,而不位于它的内存地址区。
NAND闪存(K9F1208U0A)的数据存储结构分层为:1设备(Device) = 4096 块(Block);1块= 32页/行
(Page/row);1页= 528B = 数据块 (512B) + OOB块 (16B)在每一页中,最后16个字节(又称OOB)在NAND
闪存命令执行完毕后设置状态,剩余512个字节又分为前半部分和后半部分。可以通过NAND闪存命令
00h/01h/50h分别对前半部、后半部、OOB进行定位,通过NAND闪存内置的指针指向各自的首地址。
NAND闪存的操作特点为:擦除操作的最小单位是块;NAND闪存芯片每一位只能从1变为0,而不能从
0变为1,所以在对其进行写入操作之前一定要将相应块擦除;OOB部分的第6字节为坏快标志,即如果不
是坏块该值为FF,否则为坏块;除OOB第6字节外,通常用OOB的前3个字节存放NAND闪存的硬件ECC(校
验寄存器)码;
从NAND 闪存启动U-BOOT 的设计思路
如果S3C2410被配置成从NAND闪存启动,上电后,S3C2410的NAND闪存控制器会自动把NAND闪存中
的前4K数据搬移到内部RAM中, 并把0x00000000设置为内部RAM的起始地址, CPU从内部RAM的
0x00000000位置开始启动。因此要把最核心的启动程序放在NAND闪存的前4K中。
由于NAND闪存控制器从NAND闪存中搬移到内部RAM的代码是有限的,所以, 在启动代码的前4K里,
必须完成S3C2410的核心配置,并把启动代码的剩余部分搬到RAM中运行。在U-BOOT中, 前4K完成的主
要工作就是U-BOOT启动的第一个阶段(stage1)。
根据U-BOOT的执行流程图,可知要实现从NAND闪存中启动U-BOOT,首先需要初始化NAND闪存,并从
NAND闪存中把U-BOOT搬移到RAM中,最后需要让U-BOOT支持NAND闪存的命令操作。 2开发环境
本设计中目标板硬件环境如下:CPU为S3C2410,SDRAM为HY57V561620,NAND闪存为64MB的K9F1208U0A。
主机软件环境为Redhat9.0、 U-BOOT-1.1.3、gcc 2.95.3。修改U-BOOT的Makefile,加入:
wch2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t wch2410 NULL s3c24x0
即将开发板起名为wch2410,接下来依次进行如下操作:
mkdir board/wch2410
cp board/smdk2410 board/wch2410
mv smdk2410.c wch2410.c
cp include/configs/smdk2410.h include/configs/wch2410.h
export PATH="/usr/local/arm/2".95.3/bin:$PATH
最后执行:
make wch2410_config
make all ARCH="arm"
生成u-boot.bin,即通过了测试编译。
具体设计
支持NAND 闪存的启动程序设计
因为U-BOOT的入口程序是/cpu/arm920t/start.S,故需在该程序中添加NAND闪存的复位程序,以及实
现从NAND闪存中把U-BOOT搬移到RAM中的功能程序。
首先在/include/configs/wch2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1 @支持从NAND 闪存中启动
然后在/cpu/arm920t/start.S中添加
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安装栈的起始地址
mov fp, #0 @初始化帧指针寄存器
bl nand_reset @跳到复位C函数去执行,执行NAND闪存复位
.......
/*从NAND闪存中把U-BOOT拷贝到RAM*/
ldr r0, =UBOOT_RAM_BASE@ 设置第1个参数: UBOOT在RAM中的起始地址
mov r1, #0x0 @ 设置第2个参数:NAND闪存的起始地址
mov r2, #0x20000 @ 设置第3个参数: U-BOOT的长度(128KB)
bl nand_read_whole @ 调用nand_read_whole(),把NAND闪存中的数据读入到RAM中
tst r0, #0x0 @ 如果函数的返回值为0,表示执行成功
beq ok_nand_read @ 执行内存比较,把RAM中的前4K内容与NAND闪存中的前4K内容进行比较, 如
果完全相同, 则表示搬移成功
其中,nand_reset (),nand_read_whole()被加在/board/wch2410/wch2410.c中。
支持U-BOOT 命令设计
在U-BOOT下对nand闪存的支持主要是在命令行下实现对nand闪存的操作。对nand闪存实现的命令
为:nand info(打印nand Flash信息)、nand device(显示某个nand闪存设备)、nand read(读取nand闪
存)、nand write(写nand闪存)、nand erease(擦除nand闪存)、nand bad(显示坏块)等。
用到的主要数据结构有:struct nand_flash_dev、struct nand_chip。前者包括主要的芯片型号、存
储容量、设备ID、I/O总线宽度等信息;后者是具体对NAND闪存进行操作时用到的信息。
a. 设置配置选项
修改/include/configs/wch2410.h,主要是在CONFIG_COMMANDS中打开CFG_CMD_NAND选项。定义NAND
闪存控制器在SFR区中的起始寄存器地址、页面大小,定义NAND闪存命令层的底层接口函数等。
b. 加入NAND闪存芯片型号
在/include/linux/mtd/ nand_ids.h中对如下结构体赋值进行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0A", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0},
.......
}
这样对于该款NAND闪存芯片的操作才能正确执行。
c. 编写NAND闪存初始化函数
在/board/wch2410/wch2410.c中加入nand_init()函数。
void nand_init(void)
{
/* 初始化NAND闪存控制器, 以及NAND闪存芯片 */
nand_reset();
/* 调用nand_probe()来检测芯片类型 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
该函数在启动时被start_armboot()调用。
最后重新编译U-BOOT并将生成的u-boot.bin烧入NAND闪存中,目标板上电后从串口输出如下信息:
U-Boot 1.1.3 (Nov 14 2006 - 11:29:50)
U-Boot code: 33F80000 -> 33F9C9E4 BSS: -> 33FA0B28
RAM Configuration:
Bank #0: 30000000 64 MB
## Unknown Flash on Bank 0: ID 0xffff, Size = 0x00000000 = 0 MB
Flash: 0 kB
NAND: 64 MB
In:serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
wch2410 #
结语
以往将U-BOOT移植到ARM9平台中的解决方案主要针对的是ARM9中的NOR闪存,因为NOR闪存的结构
特点致使应用程序可以直接在其内部运行,不用把代码读到RAM中,移植过程相对简单。从NAND闪存中启
动U-BOOT的设计难点在于NAND闪存需要把U-BOOT的代码搬移到RAM中,并要让U-BOOT支持NAND闪存的
命令操作。本文介绍了实现这一设计的思路及具体程序。移植后,U-BOOT在嵌入式系统中运行良好。
参考文献
1 杜春雷. ARM体系结构与编程[M]. 北京:清华大学出版社,2003
2 S3C2410 User’s Mannual[Z].Samsung
U-boot 给kernel 传参数和kernel 读取参数
—struct tag (以及补充)
U-boot 会给 Linux Kernel 传递很多参数,如:串口, RAM , videofb 等。而 Linu
x kernel 也会读取和处理这些参数。两者之间通过 struct tag 来传递参数。 U-boot 把
要传递给 kernel 的东西保存在 struct tag 数据结构中,启动 kernel 时,把这个结构体
的物理地址传给 kernel ; Linux kernel 通过这个地址,用 parse_tags 分析出传递过来
的参数。
本文主要以 U-boot 传递 RAM 和 Linux kernel 读取 RAM 参数为例进行说明。
1 、u-boot 给kernel 传RAM 参数
./common/cmd_bootm.c 文件中, bootm 命令对应的 do_bootm 函数,当分析 uI
mage 中信息发现 OS 是 Linux 时 , 调用 ./lib_arm/bootm.c 文件中的 do_bootm_lin
ux 函数来启动 Linux kernel 。
在 do_bootm_linux 函数中:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd); // 初始化 tag 结构体开始
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); // 设置 RAM 参数
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); // 初始化 tag 结构体结束
#endif
......
......
theKernel (0, machid, bd->bi_boot_params);
// 传给 Kernel 的参数= (struct tag *) 型的 bd->bi_boot_params
//bd->bi_boot_params 在 board_init 函数中初始化如对于 at91rm9200 ,初始化在 at91r
m9200dk.c 的 board_init 中进行: bd->bi_boot_params =PHYS_SDRAM + 0x100;
// 这个地址也是所有 taglist 的首地址,见下面的 setup_start_tag 函数
}
对于 setup_start_tag 和 setup_memory_tags 函数说明如下。
函数 setup_start_tag 也在此文件中定义,如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
// 初始化 (struct tag *) 型的全局变量 params 为bd->bi_boot_params 的地址,之后的setup tags 相关
函数如下面的 setup_memory_tags 就把其它 tag 的数据放在此地址的偏移地址上。
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);
}
RAM 相关参数在 bootm.c 中的函数 setup_memory_tags 中初始化:
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
} // 初始化内存相关 tag
}
2 、Kernel 读取U-boot 传递的相关参数
对于 Linux Kernel , ARM 平台启动时,先执行 arch/arm/kernel/head.S ,此文件
会调用 arch/arm/kernel/head-common.S 中的函数,并最后调用 start_kernel :
......
b start_kernel
......
init/main.c 中的 start_kernel 函数中会调用 setup_arch 函数来处理各种平台相关
的动作,包括了 u-boot 传递过来参数的分析和保存:
start_kernel()
{
......
setup_arch(&command_line);
......
}
其中, setup_arch 函数在 arch/arm/kernel/setup.c 文件中实现,如下:
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
// 指向各种 tag 起始位置的指针,定义如下:
//unsigned int __atags_pointer __initdata;
// 此指针指向 __initdata 段,各种 tag 的信息保存在这个段中。
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
// 处理各种 tags ,其中包括了 RAM 参数的处理。
// 这个函数处理如下 tags :
__tagtable(ATAG_MEM, parse_tag_mem32);
__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
}
init_mm.start_code = (unsigned long) &_text;
init_mm.end_code = (unsigned long) &_etext;
init_mm.end_data = (unsigned long) &_edata;
init_mm.brk = (unsigned long) &_end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); // 处理编译内核时指定的 cmdline 或 u-boot 传
递的 cmdline
paging_init(&meminfo, mdesc);
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
对于处理 RAM 的 tag ,调用了 parse_tag_mem32 函数:
static int __init parse_tag_mem32(const struct tag *tag)
{
......
arm_add_memory(tag->u.mem.start, tag->u.mem.size);
......
}
__tagtable(ATAG_MEM, parse_tag_mem32);
上述的 arm_add_memory 函数定义如下:
static void __init arm_add_memory(unsigned long start, unsigned long size)
{
struct membank *bank;
size -= start & ~PAGE_MASK;
bank = &meminfo.bank[meminfo.nr_banks++];
bank->start = PAGE_ALIGN(start);
bank->size = size & PAGE_MASK;
bank->node = PHYS_TO_NID(start);
}
如上可见, parse_tag_mem32 函数调用 arm_add_memory 函数把 RAM 的 start
和 size 等参数保存到了 meminfo 结构的 meminfo 结构体中。最后,在 setup_arch
中执行下面语句:
paging_init(&meminfo, mdesc);
对有 MMU 的平台上调用 arch/arm/mm/nommu.c 中的 paging_init ,否则调用 ar
ch/arm/mm/mmu.c 中的 paging_init 函数。这里暂不分析 mmu.c 中的 paging_init 函
数。
3 、关于U-boot 中的bd 和gd
U-boot 中有一个用来保存很多有用信息的全局结构体-- gd_t ( global data 缩
写),其中包括了 bd 变量,可以说 gd_t 结构体包括了 u-boot 中所有重要全局变量。
最后传递给内核的参数,都是从 gd 和 bd 中来的,如上述的 setup_memory_tags 函数
作用就是用 bd 中的值来初始化 RAM 相应的 tag 。
对于 ARM 平台这个结构体的定义大致如下:
include/asm-arm/global_data.h
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
void **jt; /* jump table */
} gd_t;
在 U-boot 中使用 gd 结构之前要用先用宏 DECLARE_GLOBAL_DATA_PTR 来
声明。这个宏的定义如下:
include/asm-arm/global_data.h
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
从这个宏的定义可以看出, gd 是一个保存在 ARM 的 r8 寄存器中的 gd_t 结构
体的指针。
说明:本文的版本为U-boot-1.3.4 、Linux-2.6.28 ,平台是ARM 。
//补充一下:
来自:http://hi.baidu.com/armfans/blog/item/306cd5035f24ff084afb514b.html
bootloader巧妙地利用函数指针及传参规范将R0:0x0,R1:机器号,R2:参数地址传递
给内核.由于R0,R1比较简单,不需要再作说明.需要花点时间了解的是R2寄存器.
R2寄存器传递的是一个指针,这个指针指向一个TAG区域.UBOOT和Linux内核之
间正是通过这个扩展了的TAG区域来进行复杂参数的传递,如 command line,文件系
统信息等等,用户也可以扩展这个TAG来进行更多参数的传递.TAG区域存放的地址,
也就是R2的值,是在/board /yourboard/youboard.c里的board_init函数中初始化的,
如在UB4020中初始化为:gd->bd->bi_boot_params = 0x30000100;,这是一个绝对地
址.
TAG区的结构比较简单,可以视为一个一个TAG的排列(数组?),每一个TAG传
递一种特定类型的参数.各种系统TAG的定义可以参考./include/asm-arm/setup.h.
下面是一个TAG区的例子:
0x30000100 00000005 54410001 00000000 00000000
0x30000110 00000000 0000000F 54410009 746F6F72
0x30000120 65642F3D 61722F76 7220306D 6F632077
0x30000130 6C6F736E 74743D65 2C305379 30303639
0x30000140 696E6920 6C2F3D74 78756E69 EA006372
0x30000150 00000004 54420005 30300040 00200000
0x30000160 00000000 00000000
我们可以看到一共有三个TAG:
第一个TAG的长度是5个字,类型是ATAG_CORE(54410001),有三个元素,均为
全零.TAG区必须以这个TAG开头.
第二个TAG的长度是F个字,类型是ATAG_CMDLINE(54410009),这是一个字符串,
是向内核传递的kernel command line
第三个TAG的长度是4个字,类型是ATAG_INITRD2(54410005),有两个元素,第
一个是start:30300040(30300000+40),第二个是size:200000(2M)
如果说还有第四个TAG,那就是末尾的两个全零,这是TAG结束的标志.
这些TAG是在./lib_arm/arm_linux.c中的do_bootm_linux函数中建立起来的.具
体建立哪些TAG,由相应的控制宏决定.具体可以参考相应代码.例子中第一个TAG是
起始TAG,如果环境变量中有bootargs,则建立第二个TAG,如果bootm有两个参数(引
导文件系统),则会读取文件系统头部的必要信息,建立第三个TAG.
内核启动后,将根据R2寄存器的值找到这些TAG,并根据TAG类型,调用相应的处
理函数进行处理,从而获取内核运行的必要信息.
U-BOOT 源码分析及移植
本文从以下几个方面粗浅地分析u-boot 并移植到FS2410 板上:
1、u-boot 工程的总体结构
2、u-boot 的流程、主要的数据结构、内存分配。
3、u-boot 的重要细节,主要分析流程中各函数的功能。
4、基于FS2410 板子的u-boot 移植。实现了NOR Flash 和NAND Flash 启动,网络功
能。
这些认识源于自己移植u-boot 过程中查找的资料和对源码的简单阅读。下面主要以
smdk2410 为分析对象。
一、u-boot 工程的总体结构:
1、源代码组织
对于ARM而言,主要的目录如下:
board 平台依赖 存放电路板相关的目录文件,每一套板子对 应一个目
录。如smdk2410(arm920t)
cpu 平台依赖 存放CPU 相关的目录文件,每一款CPU 对应一个目
录,例如:arm920t、 xscale、i386 等目录
lib_arm 平台依赖 存放对ARM 体系结构通用的文件,主要用于实现
ARM 平台通用的函数,如软件浮点。
common 通用 通用的多功能函数实现,如环境,命令,控制台相关的函数
实现。
include 通用 头文件和开发板配置文件,所有开发板的配置文件都在
configs 目录下
lib_generic 通用 通用库函数的实现
net 通用 存放网络协议的程序
drivers 通用 通用的设备驱动程序,主要有以太网接口的驱动,nand
驱动。
.......
2.makefile 简要分析
所有这些目录的编译连接都是由顶层目录的makefile 来确定的。
在执行make 之前,先要执行make $(board)_config 对工程进行配置,以确定特定于目
标板的各个子目录和头文件。
$(board)_config:是makefile 中的一个伪目标,它传入指定的CPU,ARCH,BOARD,
SOC 参数去执行mkconfig 脚本。
这个脚本的主要功能在于连接目标板平台相关的头文件夹,生成config.h 文件包含板子的配
置头文件。
使得makefile 能根据目标板的这些参数去编译正确的平台相关的子目录。
以smdk2410 板为例,执行 make smdk2410_config,
主要完成三个功能:
@在include 文件夹下建立相应的文件(夹)软连接,
#如果是ARM体系将执行以下操作:
#ln -s asm-arm asm
#ln -s arch-s3c24x0 asm-arm/arch
#ln -s proc-armv asm-arm/proc
@生成Makefile 包含文件include/config.mk,内容很简单,定义了四个变量:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
@生成include/config.h 头文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"
顶层makefile 先调用各子目录的makefile,生成目标文件或者目标文件库。
然后再连接所有目标文件(库)生成最终的u-boot.bin。
连接的主要目标(库)如下:
OBJS = cpu/$(CPU)/start.o
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a
fs/jffs2/libjffs2.a \
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
显然跟平台相关的主要是:
cpu/$(CPU)/start.o
board/$(BOARDDIR)/lib$(BOARD).a
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a
lib_$(ARCH)/lib$(ARCH).a
这里面的四个变量定义在include/config.mk(见上述)。
其余的均与平台无关。
所以考虑移植的时候也主要考虑这几个目标文件(库)对应的目录。
关于u-boot 的makefile 更详细的分析可以参照
http://blog.mcuol.com/User/lvembededsys/Article/4355_1.htm 。
3、u-boot 的通用目录是怎么做到与平台无关的?
include/config/smdk2410.h
这个头文件中主要定义了两类变量。
一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等,主要用来 决
定是否编译某些文件或者函数。
另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash 地址等参数。这些常
数参量主要用来支持通用目录中的代码,定义板子资源参数。
这两类宏定义对u-boot 的移植性非常关键,比如drive/CS8900.c,对cs8900 而言,很
多操作都是通用的,但不是所有的板子上面都有这个芯片,即使有它在内存中映射的基地址也
是平台相关的。所以对于smdk2410 板,在smdk2410.h 中定义了
#define CONFIG_DRIVER_CS8900 1 /* we have a CS8900
on-board */
#define CS8900_BASE 0x19000300 /*IO mode base address*/
CONFIG_DRIVER_CS8900 的定义使得cs8900.c 可以被编译(当然还得定义
CFG_CMD_NET 才行),因为cs8900.c 中在函数定义的前面就有编译条件判断:#ifdef
CONFIG_DRIVER_CS8900 如果这个选项没有定义,整个cs8900.c 就不会被编译了。
而常数参量CS8900_BASE 则用在cs8900.h 头文件中定义各个功能寄存器的地址。
u-boot 的CS8900 工作在IO 模式下,只要给定IO 寄存器在内存中映射的基地址,其余代
码就与平台无关了。
u-boot 的命令也是通过目标板的配置头文件来配置的,比如要添加ping 命令,就必须添加
CFG_CMD_NET 和CFG_CMD_PING 才行。不然common/cmd_net.c 就不会被编译
了。
从这里我可以这么认为,u-boot 工程可配置性和移植性可以分为两层:
一是由makefile 来实现,配置工程要包含的文件和文件夹上,用什么编译器。
二是由目标板的配置头文件来实现源码级的可配置性,通用性。主要使用的是#ifdef #else
#endif 之类来实现的。
4、smkd2410 其余重要的文件 :
include/s3c24x0.h 定义了s3x24x0 芯片的各个特殊功能寄存器(SFR)的地址。
cpu/arm920t/start.s 在flash 中执行的引导代码,也就是bootloader 中的
stage1,负责初始化硬件环境,把u-boot 从flash 加载到RAM中去,然后跳到
lib_arm/board.c 中的start_armboot 中去执行。
lib_arm/board.c u-boot 的初始化流程,尤其是u-boot 用到的全局数据结构
gd,bd 的初始化,以及设备和控制台的初始化。
board/smdk2410/flash.c 在board 目录下代码的都是严重依赖目标板,对于不同
的CPU,SOC,ARCH,u-boot 都有相对通用的代码,但是板子构成却是多样的,主要是内
存地址,flash 型号,外围芯片如网络。对fs2410 来说,主要考虑从smdk2410 板来移植,
差别主要在nor flash 上面。
二、u-boot 的流程、主要的数据结构、内存分配
1、u-boot 的启动流程:
从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,
lib_arm/board.c,
1)start.s
在flash 中执行的引导代码,也就是bootloader 中的stage1,负责初始化硬件环境,把
u-boot 从flash 加载到RAM中去,然后跳到lib_arm/board.c 中的start_armboot
中去执行。
1.1.6 版本的start.s 流程:
硬件环境初始化 :
进入svc 模式;关闭watch dog;屏蔽所有IRQ 掩码;设置时钟频率FCLK、HCLK、
PCLK;清I/D cache;禁止MMU 和CACHE;配置memory control;
重定位 :
如果当前代码不在连接指定的地址上(对smdk2410 是0x3f000000)则需要把
u-boot 从当前位置拷贝到RAM指定位置中;
建立堆栈 ,堆栈是进入C 函数前必须初始化的。
清.bss 区 。
跳到start_armboot 函数中执行 。(lib_arm/board.c)
2)lib_arm/board.c:
start_armboot 是U-Boot 执行的第一个C 语言函数,完成系统初始化工作,进入主
循环,处理用户输入的命令。这里只简要列出了主要执行的函数流程:
void start_armboot (void)
{
//全局数据变量指针gd 占用r8。
DECLARE_GLOBAL_DATA_PTR;
/* 给全局数据变量gd 安排空间*/
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
/* 给板子数据变量gd->bd 安排空间*/
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;//取u-boot 的长度。
/* 顺序执行init_sequence 数组中的初始化函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*配置可用的Flash */
size = flash_init ();
……
/* 初始化堆空间 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* 重新定位环境变量, */
env_relocate ();
/* 从环境变量中获取IP 地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太网接口MAC 地址 */
……
devices_init (); /* 设备初始化 */
jumptable_init (); //跳转表初始化
console_init_r (); /* 完整地初始化控制台设备 */
enable_interrupts (); /* 使能中断处理 */
/* 通过环境变量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循环不断执行 */
for (;;) {
main_loop (); /* 主循环函数处理执行用户命令 --
common/main.c */
}
}
初始化函数序列init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下
列注释中。
init_fnc_t *init_sequence[] = {
cpu_init, /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */
board_init, /* 基本的板级相关配置 --
board/smdk2410/smdk2410.c */
interrupt_init, /* 初始化例外处理 --
cpu/arm920t/s3c24x0/interrupt.c */
env_init, /* 初始化环境变量 -- common/env_flash.c */
init_baudrate, /* 初始化波特率设置 -- lib_arm/board.c */
serial_init, /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c
*/
console_init_f, /* 控制台初始化阶段1 -- common/console.c */
display_banner, /* 打印u-boot 信息 -- lib_arm/board.c */
dram_init, /* 配置可用的RAM --
board/smdk2410/smdk2410.c */
display_dram_config, /* 显示RAM的配置大小 -- lib_arm/board.c */
NULL,
};
整个u-boot 的执行就进入等待用户输入命令,解析并执行命令的死循环中。
2、u-boot 主要的数据结构
u- boot 的主要功能是用于引导OS 的,但是本身也提供许多强大的功能,可以通过输入命令
行来完成许多操作。所以它本身也是一个很完备的系统。u-boot 的大部分操作都是围绕它自
身的数据结构,这些数据结构是通用的,但是不同的板子初始化这些数据就不一样了。所以
u-boot 的通用代码是依赖于这些重要的数据结构的。这里说的数据结构其实就是一些全局变
量。
1)gd 全局数据变量指针,它保存了u-boot 运行需要的全局数据,类型定义:
typedef struct global_data {
bd_t *bd; //board data pointor 板子数据指针
unsigned long flags; //指示标志,如设备已经初始化标志等。
unsigned long baudrate; //串口波特率
unsigned long have_console; /* 串口初始化标志*/
unsigned long reloc_off; /* 重定位偏移,就是实际定向的位置与编译连接时
指定的位置之差,一般为0 */
unsigned long env_addr; /* 环境参数地址*/
unsigned long env_valid; /* 环境参数CRC 检验有效标志 */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
void **jt; /* 跳转表,1.1.6 中用来函数调用地址登记 */
} gd_t;
2)bd 板子数据指针 。板子很多重要的参数。 类型定义如下:
typedef struct bd_info {
int bi_baudrate; /* 串口波特率 */
unsigned long bi_ip_addr; /* IP 地址 */
unsigned char bi_enetaddr[6]; /* MAC 地址*/
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* 启动参数 */
struct /* RAM 配置 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
3)环境变量指针 env_t *env_ptr = (env_t
*)(&environment[0]);(common/env_flash.c)
env_ptr 指向环境参数区,系统启动时默认的环境参数environment[],定义在
common/environment.c 中。
参数解释 :
bootdelay 定义执行自动启动的等候秒数
baudrate 定义串口控制台的波特率
netmask 定义以太网接口的掩码
ethaddr 定义以太网接口的MAC 地址
bootfile 定义缺省的下载文件
bootargs 定义传递给Linux 内核的命令行参数
bootcmd 定义自动启动时执行的几条命令
serverip 定义tftp 服务器端的IP 地址
ipaddr 定义本地的IP 地址
stdin 定义标准输入设备,一般是串口
stdout 定义标准输出设备,一般是串口
stderr 定义标准出错信息输出设备,一般是串口
4)设备相关 :
标准IO设备数组

你可能感兴趣的:(Linux,系列)