u-boot-1.1.6源码分析
15年11月1日15:22:59
想要分析一个大的程序是从哪一个文件开始执行的,首先是分析它的Makefile,当然也可以采取一个取巧的办法,将编译过的uboot.bin文件删除,然后从新make,从它的最后的输出信息里面找到 arm-linux-ld链接命令,如下所示:
cd /home/ybx/u-boot-1.1.6 && arm-linux-ld -Bstatic -T /home/ybx/u-boot-1.1.6/board/100ask24x0/u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o .......
这样就可以轻松找到第一个执行文件,当然,对于uboot来说,没啥必要~~~
对于以下内容,所有的源代码都有灰色阴影,我所添加的注释没有阴影,对于2410里面配置的宏用红色字体标出,对于代码和注释,始终坚持代码在前,注释在后的潜规则(~.~!)严格执行~~~
(1)那么就来分析这个文件:/cpu/arm920t/start.S:
#include
#include
.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
/*
*************************************************************************
*
* 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:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
/* 这一段代码是程序的起始阶段,设置程序的中断向量表。关于这个中断向量表,我有一篇博客里面仔细分析了。一般的uboot都会将中断关掉的。那我们就直接跳到reset这个标号那里继续执行。 */
(2)这个reset是start.S中的一个标号,程序继续在/cpu/arm920t/start.S中执行:
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* 将cpu设置成管理模式,至于为什么设置成管理模式,可以参见《Uboot中start.S源码的指令级的详尽解析》 那篇文章。 */
/* 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
/* 根据不同的宏来分别设置寄存器的地址,因为uboot兼容很多单板,这个技巧很常用。 */
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/* 关闭看门狗,往寄存器里面写0即可。 */
/*
* 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
/* 关闭所有中断,对于S3C2410来说,还把子中断寄存器INTSUBMSK关掉了,往寄存器里面写1即可,这个在用户手册里面有说明。 */
/* 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!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
/* 这段代码很有意思,C语言的关键字是#ifndef,意思是如果没有定义这个宏的话,那么就执行下面的语句,要仔细看清楚了,不能与#ifdef相混淆。很显然,2410中没有定义这个宏,那么就跳转到cpu_init_crit中去执行,在这里使用的是bl跳转指令,当程序执行完cpu_init_crit的时候,再返回到这里继续往下执行。先标记好这里,一会需要返回到这。 */
(3)cpu_init_crit也是start.S中的一个标号,以下代码仍在/cpu/arm920t/start.S中:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/* 关掉I/D caches */
/*
* 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
/* 关掉MMU */
/*
* 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.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/* 这里又有一个lowlevel_init函数,跳到这个函数中去执行。可以仔细读读这个英文注释:在重定位代码前,需要先设置RAM的时钟,因为RAM时序是依赖于单板的,你能够在你的单板目录下找到一个 lowlevel_init.S文件。所以我们跳到SMDK2410的单板目录下的lowlevel_init.S文件。同时注意这个函数也是bl跳转的,执行完以后还需要跳转回来。 */
(4)跳到/board/smdk2410/lowlevel_init.S中:
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+ (B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+ (B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+ (B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+ (B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+ (B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+ (B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+ (B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
/* 进行SDRAM的初始化,关于下面这一段汇编,
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
为什么要写的这么奇怪呢?这就涉及到代码重定位的知识了。在重定位代码之前,SDRAM还没有初始化,只能使用相对地址来执行程序,同样必须使用相对跳转指令。我们的目的是将SMRDATA标号处的值一一存在以BWSCON开始的几个存储控制寄存器中,那么怎么定位SMRDATA的位置呢?先在前面用
_TEXT_BASE:
.word TEXT_BASE
来保存一个地址值,然后计算出SMRDATA与_TEXT_BASE的相对偏移量,并保存在r0中,这时候r0中的值就是SMRDATA的地址值,然后将SMRDATA开始的各个值保存在 BWSCON开始的寄存器中。用的方法是汇编中经常用的循环保存值的方法。一共13个寄存器,每个寄存器占4个字节。*/
(5)执行完lowlevel_init后,跳回到cpu_init_crit执行,从cpu_init_crit返回到/cpu/arm920t/start.S中继续执行:
#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
/* 这一段程序的目的是判断 _start 和 _TEXT_BASE 是否相同,如果程序开始执行的地方就位于程序的链接地址的话,就不用重定位了。如果是nor启动,_start即为代码的最开始,相对的0的位置;如果重新relocate代码之后,_start就是在/board/smdk2410/config.mk中定义的TEXT_BASE = 0x33F80000位置,即_start = TEXT_BASE = 0x33F80000,这样的话就不用重定位代码了。 */
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
/* 能执行到这,就说明_start 和 _TEXT_BASE 是不相同的。重定位代码就是把代码从源地址拷贝到目的地址,拷贝多大的问题。源就是r0中的值,为_start,目的就是r1中的值,为_TEXT_BASE,程序拷贝多大呢?从链接脚本里面可以看出来,用(_bss_start - _armboot_start)即为程序的大小。 */
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 */
/* 设置栈,在经过上面的重定位代码以后, r0 = TEXT_BASE = 0x33F80000,去除分给malloc堆的空间以后,再取出一部分空间给bdinfo结构体,再留出12字节给终止异常的空间,然后将这个值赋给sp,即设置好了栈。 */
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
/* 清BSS段,BSS段的大小为(_bss_end - _bss_start),将这个空间都清为0。 */
ldr pc, _start_armboot
_start_armboot: .word start_armboot
/* 将_start_armboot的值赋给pc,即调用start_armboot函数,即跳到uboot的第二阶段。 */
(6)跳转到start_armboot函数中,它位于lib_arm/board.c 中:
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
/* 在这个文件lib_arm/board.c的前面,有init_fnc_t的定义:
typedef int (init_fnc_t) (void);
其中 init_fnc_t 如下,它是一个函数类型,该类型的函数参数都为空(void),且都返回整型值(int)。
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 */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
init_fnc_t **init_fnc_ptr;
那么这里就定义了变量init_fnc_ptr,它是一个指针变量,其中存的是另一个变量B的地址,而变量B也是一个指针变量,其中存的是init_fnc_t类型的函数的地址。*/
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN – sizeof(gd_t));
/* 这个gd是啥?在本文件前面可以看到一个这样的声明:
DECLARE_GLOBAL_DATA_PTR;
这个宏在/include/asm-arm/global_data.h中是这样定义的:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
即这个gd就是 r8寄存器的值,每次调用gd都需要重新从r8寄存器中取值。至于为啥这样,我以后明白了再修改这。。。
_armboot_start 定义代码段的起始地址,定义在start.S中
_armboot_start:
.word _start
可见其中保存的是第一条指令_start的地址。编译的时候指定了TEXT_BASE为0x33F80000,所以
_armboot_start中的值也为0x33F80000。
CFG_MALLOC_LEN 也在/include/configs/smdk2410.h中定义了,如下所示:
#define CFG_MALLOC_LEN (CONFIG_ENV_SIZE + 128*1024)
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
所以==> CFG_MALLOC_LEN = 0x10000 + 128*1024 = 0x10000 + 0x20000 = 0x30000,即192k。
sizeof(gd_t) = 9 * 4 = 36 = 0x24 (这里没有VDF和LCD)。 */
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
/* __asm__ __volatile__("": : :"memory"); 是内嵌汇编的知识,
__asm__用于指示编译器在此插入汇编语句
__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的 样子处理这这里的汇编。
memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
""::: 表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。 */
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
/* 上面这些代码就是定义gd 和 bd的位置,并将这两个结构体先清零,大致的布局如下
+------------+ -> 0x34000000 sp
| 512k | -> 这个位置为了放u-boot
+------------+ -> 0x33f80000 _armboot_start
| 192k | -> CFG_MALLOC_LEN = 192k
+------------+ -> 0x33f50000
| 24k | -> sizeof(gd_t) = 0x24,为uboot的配置信息
+------------+ <------- gd就指向这里了。
| | -> sizeof(bd_t), 板级相关信息
+------------+ <------- gd->bd 就指向这里了。
| |
+------------+
*/
gd_t结构体在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;
在这个结构体里面保存了uboot的配置信息,如波特率,flags等信息。
bd_t结构体在include/asm-arm/u-boot.h中定义,如下所示:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
这个结构体保存了一些板子的信息,如IP地址,机器ID等信息。
这两个结构体贯穿了uboot的整体,很重要。
monitor_flash_len = _bss_start - _armboot_start;
/* 计算代码段的长度,根据链接脚本里面的计算的,bss段开始的地址减去程序开始的地址,就是代码段的长度。 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* 在上面说了,这个变量init_fnc_ptr,它是一个指针变量,其中存的是另一个变量B的地址,而变量B也是一个指针变量,其中存的是init_fnc_t类型的函数的地址,所以这个for循环可以表示为遍历init_sequence 这个数组里面所有的函数。那么就跳到这个数组里面,分析分析每个函数都干了什么事情。关于init_sequence这个函数数组已经在前面写出来了,在这就不阐述了。 */
<1> 首先是 cpu_init, /* basic cpu dependent setup */
这个函数在/cpu/arm920t/cpu.c里面,代码如下:
int cpu_init (void)
{
/*
* setup up stacks if necessary
*/
#ifdef CONFIG_USE_IRQ
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
return 0;
}
并没有定义CONFIG_USE_IRQ这个宏,所以对于我们来说,什么都没做。
在start.S中,对于这个宏有定义,如下所示:
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
这个函数的目的就是在uboot使用中断的情况下,设置栈的位置等信息。
<2> board_init, /* basic board dependent setup */
这个函数在board/smdk2410/smdk2410.c中定义,代码如下:
int board_init (void)
{
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;
/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
/* some delay between MPLL and UPLL */
delay (4000);
/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
/* some delay between MPLL and UPLL */
delay (8000);
/* set up the I/O ports */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;
/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;
icache_enable();
dcache_enable();
return 0;
}
上面代码中S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
这是uboot对寄存器访问的方式,对于底层的寄存器访问,uboot都采取了这种方式,好好体会:
1> 首先定义了S3C24X0_CLOCK_POWER 结构,在include/s3c24x0.h中:
typedef struct {
S3C24X0_REG32 LOCKTIME;
S3C24X0_REG32 MPLLCON;
S3C24X0_REG32 UPLLCON;
S3C24X0_REG32 CLKCON;
S3C24X0_REG32 CLKSLOW;
S3C24X0_REG32 CLKDIVN;
} /*__attribute__((__packed__))*/ S3C24X0_CLOCK_POWER;
2> 然后S3C24X0_GetBase_CLOCK_POWER()的定义在include/s3c2410.h中,如下所示,通过这个方法获取寄存器的基地址:
static inline S3C24X0_CLOCK_POWER * const S3C24X0_GetBase_CLOCK_POWER(void)
{
return (S3C24X0_CLOCK_POWER * const)S3C24X0_CLOCK_POWER_BASE;
}
3> S3C24X0_CLOCK_POWER_BASE的宏定义也在include/s3c2410.h中,为CLOCK首个寄存器的基地址:
#define S3C24X0_CLOCK_POWER_BASE 0x4C000000
4> 这样看也就好理解了,对于底层的每一个模块如clock模块,gpio模块,nand模块,uart模块等,uboot中都定义了相关的结构体,如 S3C24X0_CLOCK_POWER,S3C24X0_GPIO,S3C2410_NAND,S3C24X0_UART等,然后用上面的方法调用相应模块,
如S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
再从相应的单板中取出相应的地址值,这种内核中常用的面向对象的思想。。。。。。
5> 这个函数中对于GPIO也采用了上述方法,我在这列举如下,自行体会吧:
其中 S3C24X0_GPIO这个结构体过于庞大,我就没有列举,去include/s3c24x0.h中查看。
static inline S3C24X0_GPIO * const S3C24X0_GetBase_GPIO(void)
{
return (S3C24X0_GPIO * const)S3C24X0_GPIO_BASE;
}
#define S3C24X0_GPIO_BASE 0x56000000
总结:这个函数依次完成了:设置LOCKTIME,MPLLCON,UPLLCON,GPIO管脚,并在最后通过
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
gd->bd->bi_boot_params = 0x30000100;
设置了机器ID和uboot向内核传递参数(tag)的地址信息,这两个信息用于启动内核的时候,传给theKernel函数:
theKernel (0, machid, bd->bi_boot_params)。
然后激活了icache和dcache,这两个函数就不列举了。
<3> interrupt_init, /* set up exceptions */
int interrupt_init (void)
{
S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00;
if (timer_load_val == 0)
{
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and 15625 @ 50 MHz
*/
timer_load_val = get_PCLK()/(2 * 16 * 100);
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val;
/* auto load, manual update of Timer 4 */
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
/* auto load, start Timer 4 */
timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
timestamp = 0;
return (0);
}
这个函数实际上是设置timer定时器,2410总共有5个定时器,timer0~timer4,这里使用timer 4,原因解释的很清楚了, because it has no output。timer0~timer4有脉冲宽度调制(PWM)功能,因此这4个定时器也被称为PWM定时器。timer4是一个内部定时器,无外部输出引脚。
那么看上面的代码:
1> prescaler1
timers->TCFG0 = 0x0f00;
这里要使用timer4,所以设置其[15:8]为0f,即prescaler1 = 16
2> 定时器初值计算
timer_load_val = get_PCLK()/(2 * 16 * 100);
get_PCLK()位于cpu/arm920t/s3c24x0/speed.c 中,取得pclk的过程如下
FCLK => HCLK = FCLK / 2 => PCLK = HCLK / 2
即先通过MPLLCON的值计算得出FCLK,然后根据CLKDIVN的分频率比得出HCLK 和 PCLK。
要做一个10ms的定时器,所以这里计算初值。上面prescaler1 = 16, 且此处没有设置TCFG1,所以TCFG1为默认值0, 则divider选择了1/2; 1s = 10ms * 100;
于是,需要的三个参数全都出来了
分频后频率为 (clk /(2 * 16)),即每秒要做(clk/(2 * 16))次减计数,那么10ms就要做 clk / (2 * 16 * 100)次减计数。
3> TCNTB4
timers->TCNTB4 = timer_load_val 将值记录于TCNTB4中。在 auto reload 模式时,在减计数一次完成之后,会自动将 TCNTB4 的值赋给 TCNT4
4> TCON
最后 设置 TCON 的bit[22:20]
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000; => bit[22:20] = 110
即选择 auto reload mode, update TCNTB4。
timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; => bit[22:20] = 101
即 auto reload mode, start for timer4 此时,真正开始定时器4。
<4> env_init, /* initialize environment */
env_init函数在很多文件里面都有函数的定义,比如在env_dataflash.c, env_flash.c, env_nand.c等等,那么这个环境变量到底存在哪呢?随便打开一个文件,比如说env_nand.c中,查看的时候发现在这个文件的头部:#if defined(CFG_ENV_IS_IN_NAND),发现这是一个宏开关,肯定在2410的配置文件中定义了这些环境变量放在哪,于是又查看了env_flash.c文件,发现
#if defined(CFG_ENV_IS_IN_FLASH),追踪各个宏定义,发现在/include/config/smdk2410.h中定义了这些宏开关,发现在2410的默认代码中,环境变量是放在flash中的。于是下面的这个函数就是common/env_flash.c中的函数:
(但是发现env_flash.c中有两个env_init函数啊。。。。真是日了dog了。。。)
分析代码,发现是这样的:
#ifdef CFG_ENV_ADDR_REDUND
int env_init(void)
{
...
}
else
{
int env_init(void)
{
...
}
}
而CFG_ENV_ADDR_REDUND是在/include/environment.h中定义的,所以就用第一个env_init函数吧,因为还不知道
/include/environment.h这个头文件是怎么使用的,所以暂且这样分析。。。
int env_init(void)
{
int crc1_ok = 0, crc2_ok = 0;
uchar flag1 = flash_addr->flags;
uchar flag2 = flash_addr_new->flags;
ulong addr_default = (ulong)&default_environment[0];
ulong addr1 = (ulong)&(flash_addr->data);
ulong addr2 = (ulong)&(flash_addr_new->data);
crc1_ok = (crc32(0, flash_addr->data, ENV_SIZE) == flash_addr->crc);
crc2_ok = (crc32(0, flash_addr_new->data, ENV_SIZE) == flash_addr_new->crc);
if (crc1_ok && ! crc2_ok) {
gd->env_addr = addr1;
gd->env_valid = 1;
} else if (! crc1_ok && crc2_ok) {
gd->env_addr = addr2;
gd->env_valid = 1;
} else if (! crc1_ok && ! crc2_ok) {
gd->env_addr = addr_default;
gd->env_valid = 0;
} else if (flag1 == ACTIVE_FLAG && flag2 == OBSOLETE_FLAG) {
gd->env_addr = addr1;
gd->env_valid = 1;
} else if (flag1 == OBSOLETE_FLAG && flag2 == ACTIVE_FLAG) {
gd->env_addr = addr2;
gd->env_valid = 1;
} else if (flag1 == flag2) {
gd->env_addr = addr1;
gd->env_valid = 2;
} else if (flag1 == 0xFF) {
gd->env_addr = addr1;
gd->env_valid = 2;
} else if (flag2 == 0xFF) {
gd->env_addr = addr2;
gd->env_valid = 2;
}
return (0);
}
用了那么多if,else语句,目的就是为了设置gd->env_addr和gd->env_valid这两个变量的值,并且其中还用到了 default_environment这个数组,这个数组在common/env_common.c中定义的,
uchar default_environment[] = {
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
#endif
#ifdef CONFIG_IPADDR
"ipaddr=" MK_STR(CONFIG_IPADDR) "\0"
#endif
#ifdef CONFIG_SERVERIP
"serverip=" MK_STR(CONFIG_SERVERIP) "\0"
#endif
#ifdef CONFIG_NETMASK
"netmask=" MK_STR(CONFIG_NETMASK) "\0"
#endif
"\0"
};
如果定义了CFG_ENV_IS_IN_NAND的话,就说明一些环境变量存在nand中,这里env_init就是设置了:
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
这个函数暂时先写到这里,具体就不深入分析了,具体的关系到uboot环境变量的实现分析里面了,我在网上找了一篇文章:
uboot环境变量实现分析:http://blog.csdn.net/skyflying2012/article/details/39005705
这个环境变量的实现分析以后再继续深入研究。
<5> init_baudrate, /* initialze baudrate settings */
static int init_baudrate (void)
{
char tmp[64]; /* long enough for environment variables */
int i = getenv_r ("baudrate", tmp, sizeof (tmp));
gd->bd->bi_baudrate = gd->baudrate = (i > 0)
? (int) simple_strtoul (tmp, NULL, 10)
: CONFIG_BAUDRATE;
return (0);
}
这里面用到了getenv_r这个函数,这个函数在common/cmd_nvedit.c中定义:
int getenv_r (char *name, char *buf, unsigned len)
{
int i, nxt;
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val, n;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (-1);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
/* found; copy out */
n = 0;
while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
;
if (len == n)
*buf = '\0';
return (n);
}
return (-1);
}
这个函数又用到了env_get_char这个函数数组,
uchar (*env_get_char)(int) = env_get_char_init;
static uchar env_get_char_init (int index)
{
uchar c;
/* if crc was bad, use the default environment */
if (gd->env_valid)
{
c = env_get_char_spec(index);
} else {
c = default_environment[index];
}
return (c);
}
阅读完代码后,我们返回 getenv_r这个函数,它的大致意思是根据名字name来从 env_get_char_init中找找有没有设置baudrate,如果存在的话,就保存在buf里面返回,如果没有的话,就返回0。
那么我们再回到 init_baudrate这个函数中,如果getenv_r函数返回波特率的话,就把值存入gd->bd->bi_baudrate中,如果getenv_r函数返回0的话,就使用默认的波特率,即115200。
<6> serial_init, /* serial communications setup */
这个函数在cpu/arm920t/s3c24x0/serial.c中:
int serial_init (void)
{
serial_setbrg ();
return (0);
}
void serial_setbrg (void)
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
int i;
unsigned int reg = 0;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
/* FIFO enable, Tx/Rx FIFO clear */
uart->UFCON = 0x07;
uart->UMCON = 0x0;
/* Normal,No parity,1 stop,8 bit */
uart->ULCON = 0x3;
/*
* tx=level,rx=edge,disable timeout int.,enable rx error int.,
* normal,interrupt or polling
*/
uart->UCON = 0x245;
uart->UBRDIV = reg;
for (i = 0; i < 100; i++);
}
就是完成串口的初始化,但是这里开了FIFO,这个只是uboot源码,用在特殊的板子上的话肯定还需要修改。
<7> console_init_f, /* stage 1 init of console */
int console_init_f (void)
{
gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT;
#endif
return (0);
}
这里只是将gd->have_console置1,相当于设置了一个标识,表示将会启用uboot的命令终端。
<8> display_banner, /* say that we are here */
此函数在lib_arm/board.c里面定义:
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string);
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
return (0);
}
这个函数只执行了两句话,printf ("\n\n%s\n\n", version_string);就是打印出uboot的一些信息。
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
#ifndef CONFIG_IDENT_STRING
#define CONFIG_IDENT_STRING ""
#endif
下面分析一下这个printf函数:
printf函数在common/console.c中定义:
void printf (const char *fmt, ...)
{
va_list args;
uint i;
char printbuffer[CFG_PBSIZE];
va_start (args, fmt);
/* For this to work, printbuffer must be larger than
* anything we ever want to print.
*/
i = vsprintf (printbuffer, fmt, args);
va_end (args);
/* Print the string */
puts (printbuffer);
}
CFG_PBSIZE宏在include/config/smdk2410.h中定义:
#define CFG_PROMPT "SMDK2410 # " /* Monitor Command Prompt */
#define CFG_CBSIZE 256 /* Console I/O Buffer Size */
#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */
可以看出,printf函数中,先用vsprintf函数对字符串进行了处理,然后用puts打印出来。
vsprintf函数在lib_generic/vsprintf.c里面定义,但是我没看太懂,代码就不粘贴在这里了。
Puts函数在common/console.c中定义:
void puts (const char *s)
{
if (gd->flags & GD_FLG_DEVINIT) {
/* Send to the standard output */
fputs (stdout, s);
} else {
/* Send directly to the handler */
serial_puts (s);
}
}
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
这里使用到了gd->flags,代码的意思是根据这一位,选择从哪里输出打印信息,如果定义了GD_FLG_DEVINIT的话,就从标准输出中输出,如果没有定义的话,就从串口中打印。
<9> dram_init, /* configure available RAM banks */
dram_init函数在board/smdk2410/smdk2410.c中定义:
int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
return 0;
}
那两个宏定义在smdk2410.h中,如下所示:
#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */
#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
这个函数主要用来填充gd结构体里面的bd_t结构,bi_dram[0]表示这是板子的第一个sdram,
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;表示这个sdram的起始地址,
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;表示sdram的大小。
<10> display_dram_config,
display_dram_config这个函数在lib_arm/board.c中定义,如下所示:
static int display_dram_config (void)
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i
printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
print_size (gd->bd->bi_dram[i].size, "\n");
}
#else
ulong size = 0;
for (i=0; i
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
这个函数的作用就是打印出这个板子的RAM的一些信息,如大小等。
(7)终于讲完了init_sequence这个函数数组,它完成了一部分的初始化,让我们继续回到start_armboot这个函数中:
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */
确实没有定义CFG_NO_FLASH这个宏,所以执行下面的语句,
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start – CFG_MALLOC_LEN);
/* 将堆的内存空间清零。
mem_malloc_init这个函数在lib_arm/board.c中:
void mem_malloc_init (ulong dest_addr)
{
mem_malloc_start = dest_addr;
mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
mem_malloc_brk = mem_malloc_start;
memset ((void *) mem_malloc_start, 0,
mem_malloc_end - mem_malloc_start);
}
memset的定义如下:
void * memset(void * s,int c,size_t count)
{
char *xs = (char *) s;
while (count--)
*xs++ = c;
return s;
}
在这个程序中,_armboot_start = 0x33f80000, CFG_MALLOC_LEN是定义的堆的内存大小,将这一段空间清零。*/
(8)继续在start_armboot中执行:
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
/* nand_init函数在drivers/nand/nand.c中定义:
void nand_init(void)
{
int i;
unsigned int size = 0;
for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {
nand_init_chip(&nand_info[i], &nand_chip[i], base_address[i]);
size += nand_info[i].size;
if (nand_curr_device == -1)
nand_curr_device = i;
}
printf("%lu MiB\n", size / (1024 * 1024));
}
我们看到这里有一个for循环,有一个CFG_MAX_NAND_DEVICE宏,表示板子有几个nand flash,所以如果要移植这个uboot的话,需要在配置文件中定义这个宏,这个函数里面包含了一个nand_init_chip函数,除此之外,计算出nand flash的总大小,保存在这个nand_info结构体中,nand_curr_device表示当前nand flash设备编号,初始值为-1(在文件前面定义初始化的),因为我们只有一个nand flash 设备,所以这个值应该为0,之后打印出来这个nand flash的大小。
之后我们再进入nand_init_chip这个函数,
static void nand_init_chip(struct mtd_info *mtd, struct nand_chip *nand,
ulong base_addr)
{
mtd->priv = nand;
nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr;
board_nand_init(nand);
if (nand_scan(mtd, 1) == 0) {
if (!mtd->name)
mtd->name = (char *)default_nand_name;
} else
mtd->name = NULL;
}
在看这个函数之前,我们要看传递给这个函数的三个参数,nand_info、 nand_chip和base_address。这三个参数它们都是定义在nand.c中的三个全局变量,用于保存nand flash的相关信息,这就是初始化要的关键。nand_info主要和芯片本身相关,比如记录nand flash的大小等等。nand_chip这个结构主要记录nand flash它的操作相关,比如read、wirte等等。而base_address是记录的nand flash主控制器的寄存器基地址。它是这样定义的。
#ifndef CONFIG_SYS_NAND_BASE_LIST
#define CONFIG_SYS_NAND_BASE_LIST { CONFIG_SYS_NAND_BASE }
#endif
nand_info_t nand_info[CFG_MAX_NAND_DEVICE];
static struct nand_chip nand_chip[CFG_MAX_NAND_DEVICE];
static ulong base_address[CONFIG_SYS_MAX_NAND_DEVICE] = CONFIG_SYS_NAND_BASE_LIST;
如果没有定义CONFIG_SYS_NAND_BASE_LIST,那么寄存器基地址就是CONFIG_SYS_NAND_BASE,对于S3C2410呢这个值就为0x4E000000,所以在移植的时候又需要在配置文件中定义这个宏。
这个函数的核心就是两个函数,board_nand_init()函数和 nand_scan()函数。
这个board_nand_init()函数一看就是与板子相关的函数,我们搜遍uboot源码都没有找到这个函数,所以这个函数是需要我们实现的函数,现在参考网上的资料,也可能是在以后的uboot版本中支持了,在这列举一些这个函数的大概框架:
int board_nand_init(struct nand_chip *nand)
{
u_int32_t cfg;
u_int8_t tacls, twrph0, twrph1;
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
clk_power->CLKCON |= (1 << 4);
/* initialize hardware */
twrph0 = 3; twrph1 = 0; tacls = 0;
cfg = S3C2410_NFCONF_EN;
cfg |= S3C2410_NFCONF_TACLS(tacls - 1);
cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
NFCONF = cfg;
/* initialize nand_chip data structure */
nand->IO_ADDR_R = nand->IO_ADDR_W = (void *)0x4e00000c;
/* read_buf and write_buf are default */
/* read_byte and write_byte are default */
/* hwcontrol always must be implemented */
nand->cmd_ctrl = s3c2410_hwcontrol;
nand->dev_ready = s3c2410_dev_ready;
#ifdef CONFIG_S3C2410_NAND_HWECC
nand->ecc.hwctl = s3c2410_nand_enable_hwecc;
nand->ecc.calculate = s3c2410_nand_calculate_ecc;
nand->ecc.correct = s3c2410_nand_correct_data;
nand->ecc.mode = NAND_ECC_HW3_512;
#else
nand->ecc.mode = NAND_ECC_SOFT;
#endif
nand->options = 0;
DEBUGN("end of nand_init\n");
return 0;
}
这个函数首先置CLKCON第5位,给nand控制器供电,上电默认是供电的,然后配置NFCONF寄存器,设置nand flash读写基地址为0x4e00000c,然后填充nand_chip结构体。
nand_scan()这个函数位于drivers/nand/nand_base.c这个文件中,这段代码很长,但是不难分析,主要的目的是填充nand_chip结构体,我在后面列出了这个结构体(也是很大。。。)简单分析如下:
int nand_scan (struct mtd_info *mtd, int maxchips)
{
int i, j, nand_maf_id, nand_dev_id, busw;
struct nand_chip *this = mtd->priv;
/* Get buswidth to select the correct functions*/
busw = this->options & NAND_BUSWIDTH_16; /* 设置位宽 */
/* check for proper chip_delay setup, set 20us if not */
if (!this->chip_delay)
this->chip_delay = 20;
/* check, if a user supplied command function given */
if (this->cmdfunc == NULL)
this->cmdfunc = nand_command;
/* check, if a user supplied wait function given */
if (this->waitfunc == NULL)
this->waitfunc = nand_wait;
if (!this->select_chip)
this->select_chip = nand_select_chip;
if (!this->write_byte)
this->write_byte = busw ? nand_write_byte16 : nand_write_byte;
if (!this->read_byte)
this->read_byte = busw ? nand_read_byte16 : nand_read_byte;
if (!this->write_word)
this->write_word = nand_write_word;
if (!this->read_word)
this->read_word = nand_read_word;
if (!this->block_bad)
this->block_bad = nand_block_bad;
if (!this->block_markbad)
this->block_markbad = nand_default_block_markbad;
if (!this->write_buf)
this->write_buf = busw ? nand_write_buf16 : nand_write_buf;
if (!this->read_buf)
this->read_buf = busw ? nand_read_buf16 : nand_read_buf;
if (!this->verify_buf)
this->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
if (!this->scan_bbt)
this->scan_bbt = nand_default_bbt;
/* 以上代码就是判断这些nand_chip成员是否初始化,如果没有的话,就给它们赋值 */
/* Select the device */
this->select_chip(mtd, 0); /* 选中芯片 */
/* Send the command for reading device ID */
this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1); /* 这个一个发送命令的函数 */
/* Read manufacturer and device IDs */
nand_maf_id = this->read_byte(mtd); /* 对于三星来说,厂商ID为0xec */
nand_dev_id = this->read_byte(mtd); /* 这个是设备ID,以k9f1208为例是0x76 */
/* Print and store flash device information */
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (nand_dev_id != nand_flash_ids[i].id)
continue;
/* 根据这个设备ID:nand_dev_id从这个nand_flash_ids[i]数组中找出匹配的一项,该项上含有芯片的一些信息,这个数组在drivers/nand/nand_ids.c中,列举部分如下:
struct nand_flash_dev nand_flash_ids[] = {
{"NAND 1MiB 5V 8-bit", 0x6e, 256, 1, 0x1000, 0},
{"NAND 2MiB 5V 8-bit", 0x64, 256, 2, 0x1000, 0},
{"NAND 4MiB 5V 8-bit", 0x6b, 512, 4, 0x2000, 0},
{"NAND 1MiB 3,3V 8-bit", 0xe8, 256, 1, 0x1000, 0},
{"NAND 1MiB 3,3V 8-bit", 0xec, 256, 1, 0x1000, 0},
{"NAND 2MiB 3,3V 8-bit", 0xea, 256, 2, 0x1000, 0},
。。。
{NULL,}
};
对于此块nand,读出nand_dev_id为0x76后,则在nand_flash_ids[]中匹配到下面这一项:{"NAND 64MiB 3,3V 8-bit", 0x76, 512, 64, 0x4000, 0},由此可知,页面大小为512bytes, 芯片容量为64M, 擦写页面大小为16k等。 */
if (!mtd->name) mtd->name = nand_flash_ids[i].name;
/* mtd->name就指向字符串"NAND 64MiB 3,3V 8-bit" */
this->chipsize = nand_flash_ids[i].chipsize << 20;
/* 芯片大小为64M,即0x4000000 */
/* New devices have all the information in additional id bytes */
if (!nand_flash_ids[i].pagesize) {
。。。。/* 这个if语句不会执行,会执行else分支,所以我把代码删了 */
} else {
/* Old devices have this data hardcoded in the
* device id table */
mtd->erasesize = nand_flash_ids[i].erasesize; /*擦除页大小为16k*/
mtd->oobblock = nand_flash_ids[i].pagesize;/*页大小为512bytes*/
mtd->oobsize = mtd->oobblock / 32; /*oob大小为16bytes*/
busw = nand_flash_ids[i].options & NAND_BUSWIDTH_16; /*busw=0*/
} /* 上述这些信息都是刚才从nand_flash_ids[]中找到的,将他们赋给mtd结构体 */
/* Check, if buswidth is correct. Hardware drivers should set
* this correct ! */
if (busw != (this->options & NAND_BUSWIDTH_16)) {
printk (KERN_INFO "NAND device: Manufacturer ID:"
" 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id,
nand_manuf_ids[i].name , mtd->name);
printk (KERN_WARNING
"NAND bus width %d instead %d bit\n",
(this->options & NAND_BUSWIDTH_16) ? 16 : 8,
busw ? 16 : 8);
this->select_chip(mtd, -1);
return 1;
}
/* Calculate the address shift from the page size */
this->page_shift = ffs(mtd->oobblock) - 1;
this->bbt_erase_shift = this->phys_erase_shift = ffs(mtd->erasesize) - 1;
this->chip_shift = ffs(this->chipsize) – 1;
/* #define ffs(x) generic_ffs(x)
/* Return the position of the first bit set in 1, or 0 if none are set. The least-significant bit is position 1, the most-significant 32. */
找出第一个为1的位,最低位为1
即 0x1000 0000 0000 0000 0000 0000 0000 0000 返回32
0x0001 返回1
所以这里 ffs(mtd->oobblock) = 10 , this->page_shift = 9
this->bbt_erase_shift = this->phys_erase_shift = 13
this->chip_shift = 26 */
/* Set the bad block position */
this->badblockpos = mtd->oobblock > 512 ?
NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
/* 判断是大页还是小页,来确定坏块的标记位置, this->badblockpos大页的话=0,小页=5。 */
/* Get chip options, preserve non chip based options */
this->options &= ~NAND_CHIPOPTIONS_MSK;
this->options |= nand_flash_ids[i].options & NAND_CHIPOPTIONS_MSK;
/* Set this as a default. Board drivers can override it, if neccecary */
this->options |= NAND_NO_AUTOINCR;
/* 应该是设置nand flash的一些属性类的东西,没有深入研究。 */
/* Check if this is a not a samsung device. Do not clear the options
* for chips which are not having an extended id.
*/
if (nand_maf_id != NAND_MFR_SAMSUNG && !nand_flash_ids[i].pagesize)
this->options &= ~NAND_SAMSUNG_LP_OPTIONS;
/* Check for AND chips with 4 page planes */
if (this->options & NAND_4PAGE_ARRAY)
this->erase_cmd = multi_erase_cmd;
else
this->erase_cmd = single_erase_cmd;
/* Do not replace user supplied command function ! */
if (mtd->oobblock > 512 && this->cmdfunc == nand_command)
this->cmdfunc = nand_command_lp;
/* Try to identify manufacturer */
for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
if (nand_manuf_ids[j].id == nand_maf_id)
break;
}
break;
}
if (!nand_flash_ids[i].name) {
printk (KERN_WARNING "No NAND device found!!!\n");
this->select_chip(mtd, -1);
return 1;
}
for (i=1; i < maxchips; i++) {
this->select_chip(mtd, i);
/* Send the command for reading device ID */
this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
if (nand_maf_id != this->read_byte(mtd) ||
nand_dev_id != this->read_byte(mtd))
break;
} /* 如果有多个nand flash的话,一次去读他们的ID,但是,nand_scan的调用为 * nand_scan(mtd, 1), 即maxchips == 1,所以这一段代码不会执行。 */
if (i > 1)
printk(KERN_INFO "%d NAND chips detected\n", i);
/* Allocate buffers, if neccecary */
if (!this->oob_buf) {
size_t len;
len = mtd->oobsize << (this->phys_erase_shift – this->page_shift);
/* len = 16 << (13 - 9) = 256 */
this->oob_buf = kmalloc (len, GFP_KERNEL);
if (!this->oob_buf) {
printk (KERN_ERR "nand_scan(): Cannot allocate oob_buf\n");
return -ENOMEM;
}
this->options |= NAND_OOBBUF_ALLOC;
}
if (!this->data_buf) {
size_t len;
len = mtd->oobblock + mtd->oobsize; /* len = 512 + 16 \ 528 */
this->data_buf = kmalloc (len, GFP_KERNEL);
if (!this->data_buf) {
if (this->options & NAND_OOBBUF_ALLOC)
kfree (this->oob_buf);
printk (KERN_ERR "nand_scan(): Cannot allocate data_buf\n");
return -ENOMEM;
}
this->options |= NAND_DATABUF_ALLOC;
}
/* Store the number of chips and calc total size for mtd */
this->numchips = i; /* 芯片数目 */
mtd->size = i * this->chipsize; /* 总大小 */
/* Convert chipsize to number of pages per chip -1. */
this->pagemask = (this->chipsize >> this->page_shift) - 1;
/* Preset the internal oob buffer */
memset(this->oob_buf, 0xff, mtd->oobsize << (this->phys_erase_shift - this->page_shift));
/* If no default placement scheme is given, select an
* appropriate one */
if (!this->autooob) {
/* Select the appropriate default oob placement scheme for
* placement agnostic filesystems */
switch (mtd->oobsize) {
case 8:
this->autooob = &nand_oob_8;
break;
case 16:
this->autooob = &nand_oob_16;
break;
case 64:
this->autooob = &nand_oob_64;
break;
default:
printk (KERN_WARNING "No oob scheme defined for oobsize %d\n",
mtd->oobsize);
/* BUG(); */
}
}
/* The number of bytes available for the filesystem to place fs dependend
* oob data */
if (this->options & NAND_BUSWIDTH_16) {
mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 2);
if (this->autooob->eccbytes & 0x01)
mtd->oobavail--;
} else
mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 1);
/* 上面的代码是填充oob的一些东西。 */
/*
* check ECC mode, default to software
* if 3byte/512byte hardware ECC is selected and we have 256 byte pagesize
* fallback to software ECC
*/
this->eccsize = 256; /* set default eccsize */
this->eccbytes = 3;
switch (this->eccmode) {
case NAND_ECC_HW12_2048:
if (mtd->oobblock < 2048) {
printk(KERN_WARNING "2048 byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",
mtd->oobblock);
this->eccmode = NAND_ECC_SOFT;
this->calculate_ecc = nand_calculate_ecc;
this->correct_data = nand_correct_data;
} else
this->eccsize = 2048;
break;
case NAND_ECC_HW3_512:
case NAND_ECC_HW6_512:
case NAND_ECC_HW8_512:
if (mtd->oobblock == 256) {
printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC \n");
this->eccmode = NAND_ECC_SOFT;
this->calculate_ecc = nand_calculate_ecc;
this->correct_data = nand_correct_data;
} else
this->eccsize = 512; /* set eccsize to 512 */
break;
case NAND_ECC_HW3_256:
break;
case NAND_ECC_NONE:
printk (KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!\n");
this->eccmode = NAND_ECC_NONE;
break;
case NAND_ECC_SOFT:
this->calculate_ecc = nand_calculate_ecc;
this->correct_data = nand_correct_data;
break;
default:
printk (KERN_WARNING "Invalid NAND_ECC_MODE %d\n", this->eccmode);
/* BUG(); */
}
/* Check hardware ecc function availability and adjust number of ecc bytes per
* calculation step
*/
switch (this->eccmode) {
case NAND_ECC_HW12_2048:
this->eccbytes += 4;
case NAND_ECC_HW8_512:
this->eccbytes += 2;
case NAND_ECC_HW6_512:
this->eccbytes += 3;
case NAND_ECC_HW3_512:
case NAND_ECC_HW3_256:
if (this->calculate_ecc && this->correct_data && this->enable_hwecc)
break;
printk (KERN_WARNING "No ECC functions supplied, Hardware ECC not possible\n");
/* BUG(); */
}
mtd->eccsize = this->eccsize;
/* Set the number of read / write steps for one page to ensure ECC generation */
switch (this->eccmode) {
case NAND_ECC_HW12_2048:
this->eccsteps = mtd->oobblock / 2048;
break;
case NAND_ECC_HW3_512:
case NAND_ECC_HW6_512:
case NAND_ECC_HW8_512:
this->eccsteps = mtd->oobblock / 512;
break;
case NAND_ECC_HW3_256:
case NAND_ECC_SOFT:
this->eccsteps = mtd->oobblock / 256;
break;
case NAND_ECC_NONE:
this->eccsteps = 1;
break;
}
/* 上面的代码是设置ECC一类的东西,比如说硬件ECC或者软件ECC等。 */
/* De-select the device */
this->select_chip(mtd, -1); /* 取消片选芯片 */
/* Invalidate the pagebuffer reference */
this->pagebuf = -1;
/* Fill in remaining MTD driver data */
mtd->type = MTD_NANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;
mtd->ecctype = MTD_ECC_SW;
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
mtd->read = nand_read;
mtd->write = nand_write;
mtd->read_ecc = nand_read_ecc;
mtd->write_ecc = nand_write_ecc;
mtd->read_oob = nand_read_oob;
mtd->write_oob = nand_write_oob;
mtd->sync = nand_sync;
mtd->block_isbad = nand_block_isbad;
mtd->block_markbad = nand_block_markbad;
/* 以上的代码就是填充mtd结构体 */
/* and make the autooob the default one */
memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
/* Build bad block table */
return this->scan_bbt (mtd); /* 建立坏块列表 */
}
终于分析完了这两个函数,可以看出来里面最重要的就是nand_chip和mtd_info这两个结构体,由于这两个结构体成员众多,下面就不粘贴出来这两个结构体了,mtd_info结构体位于include/linux/mtd/mtd.h中,nand_chip结构体位于include/linux/mtd/nand.h中。*/
(9)执行完nand_init函数以后,我们继续返回到start_armboot函数中往下执行:
/* initialize environment */
env_relocate ();
/* 这个函数位于common/env_common.c文件中:
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off);
/*
* We must allocate a buffer for the environment
*/
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
/* CFG_ENV_SIZE这个宏在include/config/smdk2410.h中定义,为0x10000。这里从malloc堆里面分配出来0x10000这么大的空间,并将 env_ptr指向它。 */
/*
* After relocation to RAM, we can always use the "memory" functions
*/
env_get_char = env_get_char_memory; /* 这个函数没看懂。。。 */
if (gd->env_valid == 0) {
#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */
puts ("Using default environment\n\n");
#else
puts ("*** Warning - bad CRC, using default environment\n\n");
SHOW_BOOT_PROGRESS (-1);
#endif
if (sizeof(default_environment) > ENV_SIZE)
{
puts ("*** Error - default environment is too large\n\n");
return;
}
memset (env_ptr, 0, sizeof(env_t));
memcpy (env_ptr->data,
default_environment,
sizeof(default_environment));
#ifdef CFG_REDUNDAND_ENVIRONMENT
env_ptr->flags = 0xFF;
#endif
env_crc_update ();
gd->env_valid = 1;
}
else {
env_relocate_spec ();
}
gd->env_addr = (ulong)&(env_ptr->data);
}
/* 对于这个if语句,在前面那个init_sequence数组里面的env_init函数,它已经设置了
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
所以这个if语句不会执行,会执行它的else分支,我在上面用红色字体标出来了。
关于这个env_relocate_spec()函数,也是单板相关的函数,在common/env_nand.c中:
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 */
}
环境变量存在nand中,将其从nand中读出,并填入env_ptr指向的env_t结构里,从nand读出时,配置项中有 CFG_ENV_OFFSET,即环境变量在nand中存储的起始地址。数据读错或数据校验出错,都会使用默认的环境变量配置use_default(), 第一次运行uboot时板子会打印如下信息:
*** Warning - bad CRC or NAND, using default environment
就是因为读出的数据经过crc32校验出错(此时读出的数据不是环境变量),进而调用use_default(),在
use_default()中会打印该信息。use_default()函数同样在common/env_nand.c中,如下所示:
static void use_default()
{
puts ("*** Warning - bad CRC or NAND, using default environment\n\n");
if (default_environment_size > CFG_ENV_SIZE){
puts ("*** Error - default environment is too large\n\n");
return;
}
memset (env_ptr, 0, sizeof(env_t));
memcpy (env_ptr->data,
default_environment,
default_environment_size);
env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);
gd->env_valid = 1;
} */
env_relocate 函数的最后一行又将gd->env_addr指向env_ptr->data。
小结;env_relocate()函数所做的事情有3件:
1. 从heap中分配一段空间,用于env_t结构
2. 找到环境变量(或从内存中找或从nand中找),填充env_t结构
3. 将gd->env_addr指向env_ptr->data, 这个也就是这里的relocate所在吧。
*/
(10)继续在start_armboot函数中执行:
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 将环境变量设置完之后,紧接着就是从环境变量的相应项中获取信息,环境变量是用户与u-boot的一个交互方式,有了它之后,用户即可通过修改环境变量来修改板子的一些信息配置。这里的ip地址和网卡地址即是其中的一个典型例子。来看上面的代码, 通过getenv_IPaddr函数来获取ip地址,然后将它保存在gd->bd->bi_ip_addr里面,注意gd->bd->bi_ip_addr是ungisned long 类型,而ip地址是类似于"192.168.1.111"的字符串,我们继续往下看代码,getenv_IPaddr()函数net/net.c中:
IPaddr_t getenv_IPaddr (char *var)
{
return (string_to_ip(getenv(var)));
}
继续追踪,getenv()函数在common/cmd_nvedit.c中定义,如下所示:
char *getenv (char *name)
{
int i, nxt;
WATCHDOG_RESET();
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (NULL);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
return ((char *)env_get_addr(val));
}
return (NULL);
}
getenv("ipaddr")即在环境变量中找到ipaddr这一项对应的字符串,假设这里为"192.168.1.111"
将"192.168.1.111"传入string_to_ip。 IPaddr_t 类型是unsigned long 的一个typedef。
IPaddr_t string_to_ip(char *s)
{
IPaddr_t addr;
char *e;
int i;
if (s == NULL)
return(0);
for (addr=0, i=0; i<4; ++i) {
ulong val = s ? simple_strtoul(s, &e, 10) : 0;
addr <<= 8;
addr |= (val & 0xFF);
if (s) {
s = (*e) ? e+1 : e;
}
}
return (htonl(addr));
}
192.168.1.111 分为四个段,也就是要做4次 simple_strtoul()转换成10进制的整型
第4次转换后的值赋给val。addr是unsigned long型,32位的,将其分为4段,每8位存储ip地址中的一个段,最后addr = (((((192 << 8) | 168) << 8) | 1) << 8 ) | 111 = 0xc0a8016f。
最后一行,htonl(addr)表示主机字节顺序转换为网络字节顺序返回。那么什么叫做网络字节顺序呢?
若CPU为小端模式时,addr如下存储:
31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 192 = 0xc0 | 168 = 0xa8 | 1 = 0x01 | 111 = 0x6f |
+--------------+---------------+----------------+-----------------+
3 2 1 0
若CPU为大端模式时,addr如下存储:
31 24 23 16 15 8 7 0
+--------------+---------------+----------------+-----------------+
| 111 = 0x6f | 1 = 0x01 | 168 = 0xa8 | 192 = 0xc0 |
+--------------+---------------+----------------+-----------------+
3 2 1 0
当与另一台计算机通信时,通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)
恰恰网络字节顺序跟大端模式时相同,htonl函数就是将主机字节顺序转为网络字节顺序,在最高位字节
(MSB)-最前的系统上,这个函数什么都不做。在最低位字节(LSB)-最前的系统上它们将值转换为正确的
顺序。
最后将值返回给了gd->bd->bi_ip_addr, 所以其值应该是0x6f01800a。 */
(11)继续在start_armboot函数中执行:
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
/* 网卡地址以十六进制的形式存于gd->bd->bi_enetaddr[]数组中 */
(12) 继续在start_armboot函数中执行:
devices_init (); /* get the devices list going. */
/* 在start_armboot简单的一句话,它的底层函数很多。。。对于这个设备初始化函数同样是这样,下面慢慢来分析这个函数,这个函数在common/devices.c:
int devices_init (void)
{
#ifndef CONFIG_ARM /* already relocated for current ARM implementation */
ulong relocation_offset = gd->reloc_off;
int i;
/* relocate device name pointers */
for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
relocation_offset);
}
#endif
/* Initialize the list */
devlist = ListCreate (sizeof (device_t));
if (devlist == NULL) {
eputs ("Cannot initialize the list of devices!\n");
return -1;
}
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
#endif
#ifdef CONFIG_LCD
drv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARD
drv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFER
drv_logbuff_init ();
#endif
drv_system_init ();
#ifdef CONFIG_SERIAL_MULTI
serial_devices_init ();
#endif
#ifdef CONFIG_USB_TTY
drv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLE
drv_nc_init ();
#endif
return (0);
}
首先并没有定义这个CONFIG_ARM宏,所以执行这个#ifndef这个语句,在本文件的开头有这样的声明:
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
首先将gd->reloc_off的值赋给relocation_offset,然后重定位stdio_names[]这个数组里面的每一项。
然后就是这个ListCreate函数了,我们先分析了这个函数整体以后再分析这个ListCreate函数。这个函数的作用就是创建一个设备列表,列表里面包含所有用到的设备。
然后这个函数后面,可以看出来有很多宏,比如CONFIG_KEYBOARD,CONFIG_SERIAL_MULTI,CONFIG_USB_TTY等,如果想使用键盘,串口,和USB设备等的话,就定义这些宏。
接下来分析这个ListCreate函数。
list_t ListCreate (int elementSize)
{
list_t list;
list = (list_t) (NewHandle (sizeof (ListStruct))); /* create empty list */
if (list) {
(*list)->signature = LIST_SIGNATURE;
(*list)->numItems = 0;
(*list)->listSize = 0;
(*list)->itemSize = elementSize;
(*list)->percentIncrease = kDefaultAllocationPercentIncrease;
(*list)->minNumItemsIncrease =
kDefaultAllocationminNumItemsIncrease;
}
return list;
}
这个函数传进来的参数是sizeof (device_t),调用 NewHandle函数创建一个空的list,并将这个函数的非0返回值强制类型转换成list_t类型,然后填充这个list_t结构体。
NewHandle函数位于common/list.c中:
Handle NewHandle (unsigned int numBytes)
{
void *memPtr;
HandleRecord *hanPtr;
memPtr = calloc (numBytes, 1);
hanPtr = (HandleRecord *) calloc (sizeof (HandleRecord), 1);
if (hanPtr && (memPtr || numBytes == 0)) {
hanPtr->ptr = memPtr;
hanPtr->size = numBytes;
return (Handle) hanPtr;
} else {
free (memPtr);
free (hanPtr);
return NULL;
}
}
#define calloc(size,num) malloc(size*num)
这个NewHandle函数传进来的参数是sizeof (ListStruct),给 memPtr和 hanPtr分配了内存,如果分配成功的话,就填充 hanPtr这个结构体。
这一段用到了很多结构体,如list_t, HandleRecord等等,我就不一一列举了。
分析完底层的函数,我们继续返回到devices_init这个函数,在u-boot-1.1.6中,默认没有定义任何一个宏,但是我们肯定是需要用到这些设备中的一个或几个的,现在挑一个常用的串口设备来分析一个这个serial_devices_init()函数。这个函数在common/serial.c中:
void serial_devices_init (void)
{
device_t dev;
struct serial_device *s = serial_devices;
while (s) {
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = s->init;
dev.putc = s->putc;
dev.puts = s->puts;
dev.getc = s->getc;
dev.tstc = s->tstc;
device_register (&dev);
s = s->next;
}
}
分析代码发现,在这个文件的开头,首先定义了static struct serial_device *serial_devices = NULL;然后将这个serial_devices作为链表头,遍历这个链表中的每一项,分别为这个链表中的每一个串口设备初始化,即填充device_t这个结构体,这个结构体肯定是设备专属的结构体。既然有遍历列表的操作,那么肯定有往列表里面注册设备的操作,果然在函数的最后,有这个device_register (&dev)函数,下面分析这个注册函数。一路跟踪下去:
int device_register (device_t * dev)
{
ListInsertItem (devlist, dev, LIST_END);
return 0;
}
int ListInsertItem (list_t list, void *ptrToItem, int itemPosition)
{
return ListInsertItems (list, ptrToItem, itemPosition, 1);
}
int ListInsertItems (list_t list, void *ptrToItems, int firstItemPosition,
int numItemsToInsert)
{
int numItems = (*list)->numItems;
if (firstItemPosition == numItems + 1)
firstItemPosition = LIST_END;
else if (firstItemPosition > numItems)
return 0;
if ((*list)->numItems >= (*list)->listSize) {
if (!ExpandListSpace (list, -numItemsToInsert))
return 0;
}
if (firstItemPosition == LIST_START) {
if (numItems == 0) {
/* special case for empty list */
firstItemPosition = LIST_END;
} else {
firstItemPosition = 1;
}
}
if (firstItemPosition == LIST_END) { /* add at the end of the list */
if (ptrToItems)
memcpy (ITEMPTR (list, numItems), ptrToItems,
(*list)->itemSize * numItemsToInsert);
else
memset (ITEMPTR (list, numItems), 0,
(*list)->itemSize * numItemsToInsert);
(*list)->numItems += numItemsToInsert;
} else { /* move part of list up to make room for new item */
memmove (ITEMPTR (list, firstItemPosition - 1 + numItemsToInsert),
ITEMPTR (list, firstItemPosition - 1),
(numItems + 1 - firstItemPosition) * (*list)->itemSize);
if (ptrToItems)
memmove (ITEMPTR (list, firstItemPosition - 1), ptrToItems,
(*list)->itemSize * numItemsToInsert);
else
memset (ITEMPTR (list, firstItemPosition - 1), 0,
(*list)->itemSize * numItemsToInsert);
(*list)->numItems += numItemsToInsert;
}
return 1;
}
这些函数做的功能大致就是往设备列表list中注册第一个设备“serial”,为这个设备分配空间,然后这个链表中的设备数目增加。具体怎么执行就不分析了。
*/
(13)继续在start_armboot函数中执行:
jumptable_init ();
/* 这个 jumptable_init函数位于common/exports.c里面:
void jumptable_init (void)
{
int i;
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy;
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
gd->jt[XF_get_timer] = (void *) get_timer;
gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
gd->jt[XF_udelay] = (void *) udelay;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif /* I386 || PPC */
#if (CONFIG_COMMANDS & CFG_CMD_I2C)
gd->jt[XF_i2c_write] = (void *) i2c_write;
gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif /* CFG_CMD_I2C */
}
分析这个函数,发现它就是填充gd_t结构体里面的jt成员,具体这个成员是干嘛的,还不太了解。*/
(14)继续在start_armboot函数中执行:
console_init_r (); /* fully init console as a device */
/* console_init_r这个函数在common/console.c中,这个函数也是有两个,根据CFG_CONSOLE_IS_IN_ENV这个宏选择用哪个函数,在2410中没有定义这个宏,所以我们选择第二个console_init_r函数,如下所示:
/* Called after the relocation - use desired console functions */
int console_init_r (void)
{
device_t *inputdev = NULL, *outputdev = NULL;
int i, items = ListNumItems (devlist);
/* 通过这个 ListNumItems函数,获取list链表中的设备个数。
int ListNumItems (list_t list)
{
return (*list)->numItems;
} */
#ifdef CONFIG_SPLASH_SCREEN /* 没有定义 */
/* suppress all output if splash screen is enabled and we have
a bmp to display */
if (getenv("splashimage") != NULL)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
#ifdef CONFIG_SILENT_CONSOLE /* 没有定义 */
/* Suppress all output if "silent" mode requested */
if (gd->flags & GD_FLG_SILENT)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
/* Scan devices looking for input and output devices */
for (i = 1;
(i <= items) && ((inputdev == NULL) || (outputdev == NULL));
i++
) {
device_t *dev = ListGetPtrToItem (devlist, i);
if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
inputdev = dev;
}
if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
outputdev = dev;
}
}
/* 遍历list链表,根据每个设备对应的device_t结构体中的flags属性来选择出输入输出设备。
ListGetPtrToItem函数就是把devlist链表中的每一项都赋给dev,然后分别将 dev->flags与 DEV_FLAGS_INPUT和 DEV_FLAGS_OUTPUT所对比,以此来挑选出输入输出设备。这些都是链表的基本操作。 */
/* Initializes output console first */
if (outputdev != NULL) {
console_setfile (stdout, outputdev);
console_setfile (stderr, outputdev);
}
/* Initializes input console */
if (inputdev != NULL) {
console_setfile (stdin, inputdev);
}
/* 这里用到了console_setfile函数,这个函数在common/console.c函数中, 如下所示:
static int console_setfile (int file, device_t * dev)
{
int error = 0;
if (dev == NULL)
return -1;
switch (file) {
case stdin:
case stdout:
case stderr:
/* Start new device */
if (dev->start) {
error = dev->start ();
/* If it's not started dont use it */
if (error < 0)
break;
}
/* Assign the new device (leaving the existing one started) */
stdio_devices[file] = dev;
/*
* Update monitor functions
* (to use the console stuff by other applications)
*/
switch (file) {
case stdin:
gd->jt[XF_getc] = dev->getc;
gd->jt[XF_tstc] = dev->tstc;
break;
case stdout:
gd->jt[XF_putc] = dev->putc;
gd->jt[XF_puts] = dev->puts;
gd->jt[XF_printf] = printf;
break;
}
break;
default: /* Invalid file ID */
error = -1;
}
return error;
}
*/
先来分析这个console_setfile函数,如果在初始化设备的时候,定义了输入输出设备的话,就调用device_t函数里面的start函数,来开启该设备,如果没有定义这些设备的话,通过stdio_devices[file] = dev根据console_init_r函数来指定标准输入,标准输出,标准错误设备,这个stdio_devices在common/devices.c中是这样定义的:device_t *stdio_devices[] = { NULL, NULL, NULL };可以看到他们没有指定这些设备,所以通过
console_setfile (stdout, outputdev);
console_setfile (stderr, outputdev);
console_setfile (stdin, inputdev);
这3句话就指定好标准输入,标准输出,标准错误设备,并且将标准输出和标准错误设备指定为同一个设备,这样就可以通过输出设备来输出错误信息了。 */
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
/* 设置好这些设备以后,将gd_t结构体里面的flags添加GD_FLG_DEVINIT属性,表示设备的初始化已经完成。*/
#ifndef CFG_CONSOLE_INFO_QUIET
/* Print information */
puts ("In: ");
if (stdio_devices[stdin] == NULL) {
puts ("No input devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdin]->name);
}
puts ("Out: ");
if (stdio_devices[stdout] == NULL) {
puts ("No output devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdout]->name);
}
puts ("Err: ");
if (stdio_devices[stderr] == NULL) {
puts ("No error devices available!\n");
} else {
printf ("%s\n", stdio_devices[stderr]->name);
}
#endif /* CFG_CONSOLE_INFO_QUIET */
/* 将标准输入,标准输出,标准错误这些设备的信息打印出来。 */
/* Setting environment variables */
for (i = 0; i < 3; i++) {
setenv (stdio_names[i], stdio_devices[i]->name);
}
/* 将信息写到环境变量中去char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" }; */
return (0);
}
/* 这样就完成了console_init_r函数的分析。 */
(15)继续在start_armboot函数中执行:
/* enable exceptions */
enable_interrupts ();
/* 跟踪这个函数,同样是两个函数,如果定义CONFIG_USE_IRQ宏的话,就使用第一个,但是我们没有定义这个宏,使用第二个,第二个函数是一个空函数,所以忽略它。。。 */
/* Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
/* 2410中定义了CONFIG_DRIVER_CS8900这个宏,但是我们用的是DM9000网卡,所以暂时先不分析这个函数。 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif /* CFG_CMD_NET */
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#endif
/* 上面的函数看看就能理解了,基本也没做什么。。。 */
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
/* 到这,start_armboot函数就执行完了,可以看到,这个函数的最后是一个死循环,联想uboot的界面,可以理解,uboot一直在等待用户输入命令信息,然后解析这些命令,根据不用的命令来作出不用的操作。所以下面我们来分析这个main_loop()函数。 */
(16)main_loop()函数:
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER
static char lastcommand[CFG_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
/* 通过getenv函数,去环境变量中取bootdelay的值,如果存在的话,就采用环境变量中设置的值,如果不存在的话,就使用配置中的值CONFIG_BOOTDELAY。 */
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
s = getenv ("bootcmd"); /* 到环境变量中去取启动命令字符串。 */
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : " ");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
run_command (s, 0);
}
#endif /* CONFIG_BOOTDELAY */
/* 注意这个if语句,必须这三者同时满足条件的话才往下进行这个run_command()函数,否则跳出这个if语句,进入下面的for循环。其中用到了延时函数(abortboot),若在延时期间没有按下任何键的话,则返回0,经过!abortboot,变成1,这样函数就能继续往下执行。 */
/*
* Main Loop for Monitor Command Processing
*/
for (;;)
{
len = readline (CFG_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -2)
{
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
return; /* retry autoboot */
}
if (len == -1)
puts (" \n");
else
rc = run_command (lastcommand, flag);
if (rc <= 0)
{
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
}
/* 这个主循环中,重点是len = readline (CFG_PROMPT)函数,后面的程序都是解析这个len来分别作出不同的反应:
1> 如果readline函数返回0的话,说明输入了enter键,通过flag |= CMD_FLAG_REPEAT来添加属性,这个flags在后面作为run_command函数的一个参数。
2> 如果readline函数返回-1的话,说明按下了ctrl+c键,打印出" \n" 这句话。
3> 如果readline函数返回-2的话,说明超时了,打印出"\nTimed out waiting for command\n"这句话。
4> 如果readline函数返回值len>0的话,通过strcpy (lastcommand, console_buffer);把 console_buffer里面的值拷贝到 lastcommand中,最后执行run_command (lastcommand, flag)这个语句。
这个CFG_PROMPT在include/config/smdk2410.h中定义:
#define CFG_PROMPT "SMDK2410 # " /* Monitor Command Prompt */
下面来分析这个readline()函数:
int readline (const char *const prompt)
{
char *p = console_buffer;
int n = 0; /* buffer index */
int plen = 0; /* prompt length */
int col; /* output column cnt */
char c;
/* console_buffer在文件开头的定义如下:
char console_buffer[CFG_CBSIZE]; /* console I/O buffer */
#define CFG_CBSIZE 256 /* Console I/O Buffer Size */
所以这个数组实际上就是一个char类型的数组: console_buffer[256]。
*/
/* print prompt */
if (prompt) {
plen = strlen (prompt);
puts (prompt);
}
col = plen;
/* 先打印出来这个 prompt,在smdk2410.h我们定义它为"SMDK2410 # ",我们在使用uboot的时候,可以看到这个提示符,所以我们想要修改提示符的话,可以修改这个宏, */
for (;;) {
WATCHDOG_RESET(); /* Trigger watchdog, if needed */
c = getc();
/*
* Special character handling
*/
switch (c) {
case '\r': /* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
return (p – console_buffer);
/* 判断输入了什么,如果输入enter的话,*p = '\0', p – console_buffer = 0,所以这个readline函数返回0。 */
case '\0': /* nul */
continue;
case 0x03: /* ^C – break */
console_buffer[0] = '\0'; /* discard input */
return (-1);
/* 输入ctrl+c的话,返回-1。 */
case 0x15: /* ^U - erase line */
while (col > plen) {
puts (erase_seq);
--col;
}
p = console_buffer;
n = 0;
continue;
case 0x17: /* ^W - erase word */
p=delete_char(console_buffer, p, &col, &n, plen);
while ((n > 0) && (*p != ' ')) {
p=delete_char(console_buffer, p, &col, &n, plen);
}
continue;
case 0x08: /* ^H - backspace */
case 0x7F: /* DEL – backspace */
p=delete_char(console_buffer, p, &col, &n, plen);
continue;
default:
/*
* Must be a normal character then
*/
if (n < CFG_CBSIZE-2) {
if (c == '\t') { /* expand TABs */
puts (tab_seq+(col&07));
col += 8 - (col&07);
} else {
++col; /* echo input */
putc (c);
}
*p++ = c;
++n;
} else { /* Buffer full */
putc ('\a');
}
}
}
}
可以看出来,这个main_loop函数的核心就是run_command函数,它根据输入的不同命令,来辨别不同的操作,最后执行这个操作,所以下面分析这个run_command函数。*/
(17) run_command函数分析和uboot中命令实现方式。
run_command函数位于common/main.c中:
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CFG_CBSIZE];
char *str = cmdbuf;
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd) {
return -1; /* empty command */
}
/* 空命令 */
if (strlen(cmd) >= CFG_CBSIZE) {
puts ("## Command too long!\n");
return -1;
}
/* 判断命令的长度是否过长。 */
strcpy (cmdbuf, cmd);
while (*str) {
/*
* Find separator, or string end
* Allow simple escape of ';' by writing "\;"
*/
for (inquotes = 0, sep = str; *sep; sep++) {
if ((*sep=='\'') &&
(*(sep-1) != '\\'))
inquotes=!inquotes;
if (!inquotes &&
(*sep == ';') && /* separator */
( sep != str) && /* past string start */
(*(sep-1) != '\\')) /* and NOT escaped */
break;
}
/* 对于多个命令的处理,uboot中允许用;隔开两个命令一起输入,这处理的就是这种情况。 */
/*
* Limit the token to data between separators
*/
token = str;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
/* 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;
}
#if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif /* CFG_CMD_BOOTD */
/* run_command函数中传入的flags参数,与所定义的宏进行比对,然后执行相关的操作,这两个宏定义如下:#define CMD_FLAG_BOOTD 0x0002 /* command is from bootd */
*/
/* 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;
}
在这里,有一个很重要的结构体,cmd_tbl_t,它贯穿了命令实现的始终,在include/command.h中定义:
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
char *usage; /* Usage message (short) */
char *help; /* Help message (long) */
};
typedef struct cmd_tbl_s cmd_tbl_t;
这个结构体里面包含命令的名字,最大参数数目,是否可重复,usage和help信息,以及一个执行函数。对于这个是否可重复性是指,在uboot等待用户输入状态下,直接敲回车键,自动执行刚执行过的上一条命令,这个在uboot的使用过程中应该可以发现。
可以分析这个run_command函数,大体过程是这样的:
1> 对输入的命令进行一些简单的处理。
2> 有一个while (*str)循环,在这个循环里面,首先对命令分析,看它是否是两个命令一起输入的。
3> 然后是对命令中的宏进行处理,通过这个process_macros函数。
4> 对命令中的参数进行分析,比如说有的命令只有一个参数,如help,有的命令有2个参数,比如bootm 30000000,对于这两种不同的情况,通过这个parse_line函数进行处理,将命令分别保存在argv[0],argv[1]....中。
5> 通过find_cmd函数来找到与输入命令所匹配的命令操作。
6> 最后通过cmdtp->cmd来调用操作函数。
下面来分析parse_line函数,它在common/main.c中:
int parse_line (char *line, char *argv[])
{
int nargs = 0;
while (nargs < CFG_MAXARGS) {
/* skip any white space */
while ((*line == ' ') || (*line == '\t')) {
++line;
} /* 跳过空格 */
if (*line == '\0') { /* end of line, no more args */
argv[nargs] = NULL;
return (nargs);
}
/* 如果为空命令的话,直接让这个函数返回0,并且argv[0]=NULL。 */
argv[nargs++] = line; /* begin of argument string */
/* find end of string */
while (*line && (*line != ' ') && (*line != '\t')) {
++line;
}
if (*line == '\0') { /* end of line, no more args */
argv[nargs] = NULL;
return (nargs);
}
/* 将line填写进argv[0],argv[1]...中,并且使最后一个argv[nargs]=NULL,最后返回argv[]的个数 nargs。 */
*line++ = '\0'; /* terminate current arg */
}
/* end of while (nargs < CFG_MAXARGS) */
printf ("** Too many args (max. %d) **\n", CFG_MAXARGS);
return (nargs);
}
这个parse_line的返回值为argv[]的个数,这样run_command函数就可以根据这些返回值进行操作了。
下面分析这个find_cmd函数:
这个函数同样在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之间的区域去搜索匹配的命令,我们跟踪__u_boot_cmd_start的定义,可以在include/command.h中看到有这样的定义:
extern cmd_tbl_t __u_boot_cmd_start;
extern cmd_tbl_t __u_boot_cmd_end;
但是搜索完源码都没有发现这两个变量的定义,它们其实是在board/smdk2410/uboot.lds中定义的:
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.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) }
_end = .;
}
他们是怎么跑到链接脚本里面了?链接脚本中位于__u_boot_cmd_start和__u_boot_cmd_end之间是.u_boot_cmd段,那么这个段究竟里面是什么?我们在源码中搜索这个,可以发现如下的内容:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
以bootm 30000000为例,尽管搜索到了如上所示的内容,但是仍然没法知道调用了哪个函数,于是我们继续搜索bootm,在common/cmd_bootm.c中发现如下内容:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);
继续搜索U_BOOT_CMD这个宏,可以在include/command.h中发现如下定义:
#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}
继续以bootm为例来分析这个宏,将U_BOOT_CMD宏的内容填充进去,其中##是连词符:
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {bootm, CFG_MAXARGS, 1, do_bootm, usage, help}
usage = "bootm - boot application image from memory\n"
help = "[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
可以看出来,这个宏定义了一个cmd_tbl_t类型的变量__u_boot_cmd_bootm,这个变量带有一个属性__attribute__ ((unused,section (".u_boot_cmd"))),即将这个变量的段属性设置成.u_boot_cmd类型的,这样,所有的命令都以这种形式存在链接脚本指定的位置了。开始地址就是__u_boot_cmd_start,结束地址是__u_boot_cmd_end。所以,我们想要给uboot中添加啊一个命令的话,肯定也是构建这样的一个宏,宏里面包含命令的一些参数。
这样再返回来看这个find_cmd函数,就很好理解了。
run_command函数从find_cmd函数处继续执行,最后通过
(cmdtp->cmd) (cmdtp, flag, argc, argv)这个命令来执行与输入命令所匹配的函数。
至此,我们就分析完了run_command函数。
-
最后分析do_bootm函数。
do_bootm函数位于common/cmd_bootm.c中,在分析这个函数之前,我们先将image_header_t这个结构体分析一下,因为在这个do_bootm中会一直用到这个结构体中的变量,这个结构体位于include/image.h中,如下所示:
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
简单分析一下他的成员, ih_magic是幻数, ih_hcrc是头信息校验, ih_load是加载地址,是Image所放的位置,我们比较关心这个成员, ih_ep是入口地址,也是我们关心的一个成员, ih_dcrc是数据校验, ih_os是 Operating System,即操作系统的类型,ih_arch是架构相关的信息, ih_type是 映像的类型。
下面继续分析do_bootm函数:
bootm这个命令用于启动一个操作系统映像,它会从映像文件的头部取得一些信息,这些信息包括:
映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、
映像文件运行的入口地址、映像文件名等。
紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后
跳到入口地址进入内核。
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong addr;
ulong data, len, checksum;
ulong *len_ptr;
uint unc_len = CFG_BOOTM_LEN;
int i, verify;
char *name, *s;
int (*appl)(int, char *[]);
image_header_t *hdr = &header;
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
/* 从环境变量中获取"verify",最后这个信息需要传到do_bootm_linux函数中。 */
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
/* 如果输入的命令的argc < 2的话,即直接输入bootm命令,此时映像存储地址使用默认的load_addr:
ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */
#define CFG_LOAD_ADDR 0x33000000 /* default load address */
在前面是这样定义的,有一个默认的映像存储地址。但是在uboot运行期间,load_addr的值会被环境变量所更改。如果bootm后面还带有一个参数,如bootm 30000000,将这个30000000存到addr中。 */
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
read_dataflash(addr, sizeof(image_header_t), (char *)&header);
} else
#endif
/* 上面这个宏没有定义,所以不会执行。 */
memmove (&header, (char *)addr, sizeof(image_header_t));
/* 将我们输入的这个addr的值存到header中,这样就可以使用这个hdr了。 */
if (ntohl(hdr->ih_magic) != IH_MAGIC) {
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
SHOW_BOOT_PROGRESS (2);
/* 如果hdr里面的ih_magic不等于预定义的幻数的话,就打印出“Bad Magic Number”并返回。
#define IH_MAGIC 0x27051956 /* Image Magic Number */
*/
data = (ulong)&header;
len = sizeof(image_header_t);
/* 现在这个data里面保存的就是我们所输入的这个addr地址。len是image_header_t 结构体的长度。 */
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
/* hdr->ih_hcrc里面保存的是头信息校验信息,用crc32校验它是否正确。 */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
len = ntohl(hdr->ih_size) + sizeof(image_header_t);
read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
addr = CFG_LOAD_ADDR;
}
#endif
/* 没有定义这个宏,不会执行。 */
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);
/* 打印出一些信息,如Image类型,创建时间,加载地址等。 */
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
#if defined(__PPC__)
if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-4);
return 1;
}
SHOW_BOOT_PROGRESS (5);
/* 上面这一段代码没看懂想要检查什么,这些宏都没有定义。 */
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[0]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
for (i=1; len_ptr[i]; ++i)
data += 4;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-5);
return 1;
}
SHOW_BOOT_PROGRESS (6);
/* hdr->ih_type里面保存的是映像的类型,这里是先检查映像的类型,有kernel,ramdisk,multi,firmware等类型,根据不同的类型来获得真正的Image data(即不包括头信息的image)的起始地址(data)和大小(len)。 */
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts(); /* 关中断 */
#ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif
/* 未定义的宏,不会执行。 */
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);
/* hdr->ih_comp里面保存的是image的压缩方式,IH_COMP_NONE 表示未压缩的image,IH_COMP_GZIP 表示gzip的压缩方式,IH_COMP_BZIP2 表示gzip2的压缩方式,根据压缩方式来选择将要执行的动作,对于 IH_COMP_NONE类型的,判断它的加载地址是否与它头信息里面的加载地址相同,如果相同打印出"XIP %s ... ",如果不同,则调用 memmove函数,将image移动到头信息执行的加载地址。 */
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts();
/* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
char buf[32];
sprintf(buf, "%lX", len);
setenv("filesize", buf);
return 0;
}
appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
(*appl)(argc-1, &argv[1]);
return 0;
case IH_TYPE_KERNEL:
case IH_TYPE_MULTI:
/* handled below */
break;
default:
if (iflag)
enable_interrupts();
printf ("Can't boot image type %d\n", hdr->ih_type);
SHOW_BOOT_PROGRESS (-8);
return 1;
}
SHOW_BOOT_PROGRESS (8);
/* hdr->ih_type里面保存的是映像的类型,在前面已经设置过一部分内容,现在这里再设置一些内容。 */
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#if (CONFIG_COMMANDS & CFG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
}
SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
return 1;
}
/* hdr->ih_os里面保存的是操作系统的类型,根据不同的操作系统类型进去不同的入口中,对于linux,就进入了相应的do_bootm_linux函数中。 */
-
下面进入这个do_bootm_linux函数中,注意这个函数在很多文件里面都有定义,它是架构相关的,我们需要进入的是lib_arm/armlinux.c中的这个:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
/* 将这个函数指针指向入口地址。 */
/*
* Check if there is an initrd image
*/
if (argc >= 3) {
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16);
printf ("## Loading Ramdisk Image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
memcpy (&header, (char *) addr, sizeof (image_header_t));
if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
/* 检验 bootargs里面是否有initrd的信息,如果有的话,对这些信息进行处理。 */
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (unsigned char *) data, len) != checksum) {
printf ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
/* 再次做一些检验等信息。。。之前在do_bootm中做过一些。 */
if (verify) {
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (unsigned char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK\n");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) {
printf ("No Linux ARM Ramdisk Image\n");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */
/* 没有定义这些宏,不会执行。 */
/*
* Now check if we have a multifile image
*/
} /* end if (argc >= 3) */
else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
if (data) {
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
#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);
#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);
#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);
#endif
/* 上面就是设置taglist,这些参数是要传给内核的,关于这些tag的设置,在自己写bootloader中写的很清楚了,在这就不写了。 */
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
/* 进入内核前,需要关中断,关i/d-cache。 */
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
在这个函数中,重要的就是为内核执行入口地址,设置taglist,最后调用这个theKernel函数,我在程序中用黄色背景标出了。这样,就可以启动内核了。
- u-boot下mkconfig脚本具体含义...
- SHTML是什么_SSI有什么用...
- 卡尔曼滤波的原理说明...
- shell中字符串操作
- 关于java中的“错误:找不到或...
- linux设备驱动归纳总结...
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...