我们先来分析uboot启动代码的第一阶段,以arm920t为例。
#include <config.h>
#include <version.h>
#include <status_led.h>
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
.globl _start //定义一个全局变量或者函数,该处指的是函数。
_start: b start_code 复位向量 0x0 //定义的各种异常向量表,为每种异常指定了一个地址
ldr pc, _undefined_instruction 未定义指令异常向量 0x4 //当异常到来时,就跑到该异常向量地址出去执行
ldr pc, _software_interrupt 软中断向量 0x8
ldr pc, _prefetch_abort 欲取指令异常向量 0xc
ldr pc, _data_abort 数据操作异常向量 0x10
ldr pc, _not_used 未使用 0x14
ldr pc, _irq 外中断异常向量 0x18
ldr pc, _fiq 快中断异常向量 0x1c
_undefined_instruction: .word undefined_instruction 赋值操作 以下同,即_undefined_instruction = 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 //地址对齐
//当处理器碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序
继续执行。 相应的处理函数在最下面。arm有7种运行模式,当进入某一种异常模式的时候,比如是中断模式。
1 将r0—r12寄存器的值保存在该异常模式的栈里,(事先之前就要将该模式下的栈设置好,放在r13)
2 异常模式下的r14保存用户模式下即将执行指令的地址,为当前PC值加4或者加8
3 cpsr保存在该异常模式下的spsr里
4 cpsr的工作模式被设为这个异常工作模式 比如由 10000 --》 10011
5 PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中,比如 pc= 0x18
当异常处理完毕后,ARM会执行以下几步操作从异常返回:
(1)将连接寄存器LR的值减去相应的偏移量后送到PC中
(2) 将SPSR复制回CPSR中
(3) 若在进入异常处理时设置了中断禁止位,要在此清除
/*
*************************************************************************
*
* Startup Code (called from the ARM reset exception vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
*/
//以下是保存变量的数据区
_TEXT_BASE: //赋值,将uboot在内存的起始基地址TEXT_BASE赋值给_TEXT_BASE
.word TEXT_BASE
.globl _armboot_start //定义一个全局函数变量_armboot_start,将_start的值赋给_srmboot_start ,
_armboot_start: 无论是nor flash启动 nand flash启动还是内存启动_start的值为TEXT_BASE
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start //_bss_start=_bss_start(链接脚本的地址)
_bss_start:
.word __bss_start
.globl _bss_end // _bss_end = _end(链接脚本的地址)
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START //IRQ_STACK_START = 0x0badc0de
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START //FIQ_STACK_START = 0x0badc0de
FIQ_STACK_START:
.word 0x0badc0de
#endif
//保存变量的数据区完毕
/*
* the actual start code
*/
start_code: 一上电复位,便跑到这里
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr //设置cpsr_c的那个字节,设置成1101 0011 即屏蔽外中断 屏蔽快中断 arm状态 svc管理模式 即超级保护模式,可以访问被保护的资源
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
bl coloured_LED_init //在原始的uboot源码中是没有此处的
bl red_LED_on
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) || defined(CONFIG_AT91RM9200DF)
/*
* relocate exception table
*/
ldr r0, =_start //暂不清楚 应该是平台不同的限制
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
//关闭看门狗 关闭所有中断 初始化始终 都是通过设置相关的控制寄存器
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
ldr r0, =pWTCON //向看门寄存器写0 即关闭看门狗
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!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
//关闭mmu 屏蔽指令缓存和数据缓存 设置cpu运行速率和时钟初始化 ram的初始化,下面有分析!!!
bl cpu_init_crit
#endif
#ifndef CONFIG_AT91RM9200
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
//在重定位之前,一定要先初始化ram
//以下是重定位,即将uboot源码从flash上拷贝到sdram上运行,此处考虑的是nor型flash,没有考虑nand型flash
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */ //adr指令是读入代码当前运行位置,绝对位置 也即此时此刻的位置,非常非常重要
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ 若此时是在nor flash进行,则-start的值为0x0
cmp r0, r1 //如果ro=r1 即说明uboot是通过仿真器直接烧入到内存中,直接从内存启动 否则则说明是从flash运行的 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //_armboot_start存放的是_start的值,此是是_TEXT_BASE
ldr r3, _bss_start //此处也是绝对地址 他们都是链接脚本u-boot.lds中定义的
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] */ 从源地址[r0]读取32个字节到寄存器,并更新r0
stmia r1!, {r3-r10} /* copy to target address [r1] */ 拷贝寄存器r3-r10的32个字节值保存到[r1]指明的地址,并更新r1的值
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
// 重定位完毕
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
#endif
/* Set up the stack */
我们将uboot拷贝到内存之后,就要设置栈
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 */
// TEXT_BASE- CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - CONFIG_STACKSIZE_IRQ - CONFIG_STACKSIZE_FIQ -12
即内存基地址分别减去 堆空间 gd_t gd空间 外中断设置的栈空间 快中断设置的栈空间
//清bss段
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
//到此进行跳转到内存里的uboot第二阶段 终于要执行C语言入口函数了start_armboot,重要!!!
ldr pc, _start_armboot /*lib_arm/board.c 文件中 */
_start_armboot: .word start_armboot
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
cpu_init_crit
1、关闭MMU和CPU 内部指令/数据 (I/D)cache。
2、设置CPU 的速度和时钟频率。
3 、RAM 初始化。
#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 */
/*
* 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
/*
* 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
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) || defined(CONFIG_AT91RM9200DF)
#else
bl lowlevel_init //ram的初始化 就是配置13个寄存器的值 在lowlevel_init.S
#endif
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)
sub r2, r2, #(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 r7, sp, #S_PC
stmdb r7, {sp, lr}^ @ Calling SP, LR
str lr, [r7, #0] @ Save calling PC
mrs r6, spsr
str r6, [r7, #4] @ Save CPSR
str r0, [r7, #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)
sub r13, r13, #(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 //以下是每种的异常处理
*/
.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
第一阶段分析小结:
设置异常向量表
进入svc管理模式 arm状态
关看门狗 关中断 时钟初始化
cpu初始化(关mmu 关数据和指令缓存 cpu速率)和内存初始化
//关闭mmu 屏蔽指令缓存和数据缓存 设置cpu运行速率和时钟初始化 ram的初始化
重定位
设置栈
清bss断
跳入到start_armboot函数 ,lib_arm/board.c 中
在这里严重关注: 我们有两种启动方式,nand启动和nor启动,我们上面的重定位是nor型
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */ //adr指令是读入代码的当前位置,绝对位置 也即此时此刻的位置,非常非常重要
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1
//如果ro=r1 即说明uboot是通过仿真器直接烧入到内存中,直接从内存启动
否则则说明是从flash运行的 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //_armboot_start存放的是_start的值,是绝对地址
ldr r3, _bss_start //此处也是绝对地址 他们都是链接脚本u-boot.lds中定义的
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] */ 从源地址[r0]读取32个字节到寄存器,并更新r0
stmia r1!, {r3-r10} /* copy to target address [r1] */ 拷贝寄存器r3-r10的32个字节值保存到[r1]指明的地址,并更新r1的值
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
对于nand启动,是首先我们nand 4k内容被copy至内部4k ram里,执行,然后执行到重定位时,
从nand上拷贝uboot镜像到SDRAM的33f80000处
//#define S3C2440_NAND_BASE 0x4E000000
//取得Nand Flash设置寄存器的地址
mov r1, #S3C2440_NAND_BASE
//将R2设为0xFFF0
ldr r2, =0xfff0 // initial value tacls=3,rph0=7,rph1=7
//#define oNFCONF 0x00
//读取Nand Flash设置寄存器中的值到R3中
ldr r3, [r1, #oNFCONF]
//将R3或上R2后保存到R3中
orr r3, r3, r2
//将R3中的值保存到Nand Flash设置寄存器中
//TWRPH0 - 111 - Duration = HCLK * (TWRPH0 + 1)
//TACLS - 11 - Duration = HCLK * TACLS
str r3, [r1, #oNFCONF]
//#define oNFCONT 0x04
//读取Nand Flash控制寄存器中的值到R3中
ldr r3, [r1, #oNFCONT]
//将R3的[0]位置1
orr r3, r3, #1 // enable nand controller
//将R3中的值保存到Nand Flash控制寄存器中
//Mode - 1:Nand Flash Controller Enable
str r3, [r1, #oNFCONT]
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
// copy u-boot to RAM
//读取虚拟起始地址到R0中,作为目标地址
ldr r0, _TEXT_BASE
//将R1设为0,作为源地址
mov r1, #0x0
//将UBOOT大小的值保存在R2中,作为数据大小
mov r2, #CFG_UBOOT_SIZE
//跳转到nand_read_ll处执行
//并将下一条指令的地址保存在LR中
bl nand_read_ll
nand_read_ll的原型为
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
之前设置的R0 R1 R2为它的3个参数
/************************************************************************************/
/**************************** 开始第二阶段分析 **************************************/
/************************************************************************************/
开始第二阶段
第二阶段是在lib_arm/board.c bootm.c两个文件
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。
看board.c文件
#include <common.h>
#include <command.h>
#include <malloc.h>
#include <devices.h>
#include <version.h>
#include <net.h>
#include <serial.h>
#include <nand.h>
#include <onenand_uboot.h>
#ifdef CONFIG_DRIVER_SMC91111
#include "../drivers/net/smc91111.h"
#endif
#ifdef CONFIG_DRIVER_LAN91C96
#include "../drivers/net/lan91c96.h"
#endif
DECLARE_GLOBAL_DATA_PTR;
ulong monitor_flash_len;
#ifdef CONFIG_HAS_DATAFLASH
extern int AT91F_DataflashInit(void);
extern void dataflash_print_info(void);
#endif
#ifndef CONFIG_IDENT_STRING
#define CONFIG_IDENT_STRING ""
#endif
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
#ifdef CONFIG_DRIVER_CS8900
extern void cs8900_get_enetaddr (uchar * addr);
#endif
#ifdef CONFIG_DRIVER_RTL8019
extern void rtl8019_get_enetaddr (uchar * addr);
#endif
#if defined(CONFIG_HARD_I2C) || \
defined(CONFIG_SOFT_I2C)
#include <i2c.h>
#endif
/*
* Begin and End of memory area for malloc(), and current "brk"
*/
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;
static
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);
}
void *sbrk (ptrdiff_t increment)
{
ulong old = mem_malloc_brk;
ulong new = old + increment;
if ((new < mem_malloc_start) || (new > mem_malloc_end)) {
return (NULL);
}
mem_malloc_brk = new;
return ((void *) old);
}
/************************************************************************
* Coloured LED functionality
************************************************************************
* May be supplied by boards if desired
*/
void inline __coloured_LED_init (void) {}
void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
void inline __red_LED_on (void) {}
void inline red_LED_on (void) __attribute__((weak, alias("__red_LED_on")));
void inline __red_LED_off(void) {}
void inline red_LED_off(void) __attribute__((weak, alias("__red_LED_off")));
void inline __green_LED_on(void) {}
void inline green_LED_on(void) __attribute__((weak, alias("__green_LED_on")));
void inline __green_LED_off(void) {}
void inline green_LED_off(void)__attribute__((weak, alias("__green_LED_off")));
void inline __yellow_LED_on(void) {}
void inline yellow_LED_on(void)__attribute__((weak, alias("__yellow_LED_on")));
void inline __yellow_LED_off(void) {}
void inline yellow_LED_off(void)__attribute__((weak, alias("__yellow_LED_off")));
/************************************************************************
* Init Utilities *
************************************************************************
* Some of this code should be moved into the core functions,
* or dropped completely,
* but let's get it working (again) first...
*/
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);
}
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);
#ifdef CONFIG_MODEM_SUPPORT
debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
return (0);
}
/*
* WARNING: this code looks "cleaner" than the PowerPC version, but
* has the disadvantage that you either get nothing, or everything.
* On PowerPC, you might see "DRAM: " before the system hangs - which
* gives a simple yet clear indication which part of the
* initialization if failing.
*/
static int display_dram_config (void) //打印显示ram的配置信息
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i<CONFIG_NR_DRAM_BANKS; 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<CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
puts ("Flash: ");
print_size (size, "\n");
}
#endif /* CFG_NO_FLASH */
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
static int init_func_i2c (void)
{
puts ("I2C: ");
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
puts ("ready\n");
return (0);
}
#endif
/*
* Breathe some life into the board...
*
* Initialize a serial port as console, and carry out some hardware
* tests.
*
* The first part of initialization is running from Flash memory;
* its main purpose is to initialize the RAM so that we
* can relocate the monitor code to RAM.
*/
/*
* All attempts to come up with a "common" initialization sequence
* that works for all boards and architectures failed: some of the
* requirements are just _too_ different. To get rid of the resulting
* mess of board dependent #ifdef'ed code we now make the whole
* initialization sequence configurable to the user.
*
* The requirements for any new initalization function is simple: it
* receives a pointer to the "global data" structure as it's only
* argument, and returns an integer return code, where 0 means
* "continue" and != 0 means "fatal error, hang the system".
*/
typedef int (init_fnc_t) (void);
int print_cpuinfo (void); /* test-only */
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 */ 打印uboot相关信息
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
//配置可用的ram,非常重要,在这里开始进行配置DRAM信息,用到结构体bd->bi_dram[BANK_NR].start
bd->bi_dram[BANK_NR].size
display_dram_config, //打印显示ram的配置信息
NULL,
};
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/cmd_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,
};
void start_armboot (void) //start_armboot函数 非常重要,c function第一个函数!!!
{
init_fnc_t **init_fnc_ptr;
char *s;
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
//在重定位之后,即uboot的代码从flash拷到sdram 此时连接脚本里的_start 等于TEXT_BASE
/* Pointer is writable since we allocated a register for it */
// 给全局变量gd分配空间大小且指定gd的位置 这里gd是一个结构体,在uboot内存分布中
是CFG_GBL_DATA_SIZE一共128字节
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t)); //gd指针所指向的空间清零
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //给gd中的bd指针分配空间大小
memset (gd->bd, 0, sizeof (bd_t)); //gd->bd 所指向的空间清零
gd->flags |= GD_FLG_RELOC;
monitor_flash_len = _bss_start - _armboot_start; //uboot镜像文件的大小
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { //执行初始化序列函数
这里是调用了一系列的c函数指针,进行初始化。比如cpu_init初始化完成各个gpio管脚初始化,board_init完成arch_number设置和boot_params约定存放地址,还有串口初始化等。
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
board/smdk2410/flash.c配置flash
#ifndef CFG_NO_FLASH 识别出来是哪一种flash nor还是nand 如果定义了CFG_NO_FLASH这个宏,说明是nand 否则是nor
/* configure available FLASH banks */
size = flash_init (); //nor型flash的初始化
display_flash_config (size);
#endif /* CFG_NO_FLASH */
定义显示类型 分vfd和lcd两种。vfd一般不用,我们用lcd的
在这里定义了帧缓冲,也就显存的的地址和大小
-----------------------------------------------------------------------------------------------------------
#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096 //定义页大小4k
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */
#ifdef CONFIG_LCD //在内存中配置一块帧缓冲区
/* board init may have inited fb_base */
if (!gd->fb_base) {
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
#endif
/*
* reserve memory for LCD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); 按页对其方式保留显存
size = lcd_setmem (addr);分配帧缓冲区的大小
gd->fb_base = addr; //帧缓冲区的物理起始地址
}
#endif /* CONFIG_LCD */
--------------------------------------------------------------------------------------------------------------------
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //分配堆空间大小 这样才可以初始化环境变量
//初始化nand flash,这是在nand flash启动的s3c2410移植u-boot的关键,根据flash时序编写函数即可
//在include/configs/smdk2410.h中的command definition中增加CONFIG_COMMANDS和CFG_CMD_NAND命令
//nand型flash的初始化
#if defined(CONFIG_CMD_NAND)
puts ("NAND: "); 打印标志: NAND: 64MB
nand_init(); /* go init the NAND */ //board/smdk2410/smdk2410.c,获取nand的基地址和 大小信息
#endif
#if defined(CONFIG_CMD_ONENAND) 三星的一种特别的flash onenand,类似于nand
onenand_init();
#endif
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment */
env_relocate ();
//环境变量的初始化 该函数是在commen/env_commen.c中定义的,在该文件中还定义了一个默认下的环境变量
default_enviornment[] 第一次启动时,nand 默认里面是没有环境变量的,则根据板文件的宏选用默认的环境变量。
可以进行修改通过saveenv进行保存到nand里。
//env_relocate将环境变量从存储设备中读取到全局变量env_t env_ptr的data里面,由于该函数从以上堆空间
//中分配空间,所以我们的环境变量都放在了堆空间里面了
env_relocate ----> env_ptr = (env_t *)malloc (CONFIG_ENV_SIZE); gd->env_addr = (ulong)&(env_ptr->data);此处是告诉
环境变量存放在内存中的地址自此, 环境变量已经存放在内存gd->env_addr处,这样就可以获取环境变量或者
修改环境变量保存到nand中去了
#ifdef CONFIG_VFD //framebuffer初始化
/* must do this after the framebuffer is allocated */
drv_vfd_init(); //video的初始化
#endif /* CONFIG_VFD */
#ifdef CONFIG_SERIAL_MULTI //多串口
serial_initialize();
#endif
//从环境变量中获取IP地址 以太网接口MAC地址 主要是初始化 gd->bd->bi_ip_addr和gd->bd->bi_enetaddr[]
-----------------------------------------------------------------
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); 通过读取环境变量的ip地址
/* 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;
}
#ifdef CONFIG_HAS_ETH1 如果要是有两块以太网卡
i = getenv_r ("eth1addr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
#endif
}
----------------------------------------------------------------------------
devices_init (); /* get the devices list going. */ 注册设备链表,其实也就只注册了一个串口设备
#ifdef CONFIG_CMC_PU2
load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
jumptable_init ();
console_init_r ();/* fully init console as a device */
//控制台设备的初始化阶段2 到这里终于可以从控制台上看到数据打印出来了
------------------------------------------------------------------------------------------------------
注意:从串口寄存器的设置到最终在终端上打印信息,是有以下函数组成的。
在init_sequense里的三个函数和
init_baudrate, 设置 gd->bd->bi_baudrate
serial_init, 直接调用serial_setbrg函数初始化UART寄存器:8个数据位,一个开始位,一个停止位,无校验位。。。
console_init_f, 控制台前期初始化 设置gd->have_console=1
devices_init, 调用drv_system_init 注册串口设备
console_init_r 控制台后期初始化,将串口设备指向控制台标准输入设备,标准输出设备,标准错误设备
In: serial
Out: serial
Err: serial
打印这三行信息,表明串口作为标准输入设备,标准输出设备,标准错误输出设备,这样就能打印信息了
默认情况下,键盘和鼠标默认为标准输入设备,显示器默认为标准输出设备和标准错误输出设备,printf为标准格式输出
scanf为标准格式输入。标准输入,标准输出设备的重定向即将串口设备作为标准输入设备,将串口做为标准输出设备和
标准错误输出设备
static void drv_system_init (void)
static void drv_system_init (void)
{
device_t dev; //定义一个设备结构体
memset (&dev, 0, sizeof (dev));//为刚刚定义的结构体分配内存
strcpy (dev.name, "serial"); //名称
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; 设备的类型
#ifdef CONFIG_SERIAL_SOFTWARE_FIFO
dev.putc = serial_buffered_putc;
dev.puts = serial_buffered_puts;
dev.getc = serial_buffered_getc;
dev.tstc = serial_buffered_tstc;
#else
dev.putc = serial_putc;
dev.puts = serial_puts;
dev.getc = serial_getc;
dev.tstc = serial_tstc;
#endif
填充该设备结构体并注册
device_register (&dev);//注册函数 将该串口设备注册到devlist
#ifdef CFG_DEVICE_NULLDEV
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, "nulldev");
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
dev.putc = nulldev_putc;
dev.puts = nulldev_puts;
dev.getc = nulldev_input;
dev.tstc = nulldev_input;
device_register (&dev);
#endif
}
console_init_r函数
2 int console_init_r (void)
3 {
4 device_t *inputdev = NULL, *outputdev = NULL; 定义两个设备
5 int i, items = ListNumItems (devlist); // 取得设备链中的设备数 因为只注册了一个设备,即items=1
6 /* Scan devices looking for input and output devices */
7 for (i = 1;
8 (i <= items) && ((inputdev == NULL) || (outputdev == NULL));
9 i++
10 ) {
11 device_t *dev = ListGetPtrToItem (devlist, i); 从devlist中获取该串口设备
12 if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) { 执行
13 inputdev = dev; 即输入设备为该串口设备
14 }
15 if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) { 执行
16 outputdev = dev; 即输出设备也为该串口设备
17 }
18 }
// 7~18行,在设备链中按注册的顺序查找输入输出设备,在设备注册时 dev.flags表示此设备的类型。
// 比如这里drv_system_init,此设备是第一个注册的设备,且其dev.flags为
// DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM 所以上面18行过后,输入输出设备都指定为
// drv_system_init里注册的设备了
console_setfile 是最关键的地方,在这里才是真正的给我们的标准输入设备,标准输出设备,标准错误输出设备填充结构
19 /* Initializes output console first */
20 if (outputdev != NULL) {
通过console_setfile()函数可以看出,控制台有一个包含 3 个 device_t 元素的数组stdio_devices,分别对应
stdin,stdout,stderr。
通过 stdio_devices[file] = dev 就可以将dev设成设置控制台的某个设备。这样就实现了控制台任意选择设备的功能。
这和 linux 的设计思想有点类似。
21 console_setfile (stdout, outputdev); 即stdio_devices[stdout]=outputdev
22 console_setfile (stderr, outputdev); 即stdio_devices[stderr]=outputdev
23 }
24 /* Initializes input console */
25 if (inputdev != NULL) {
26 console_setfile (stdin, inputdev); 即stdio_devices[stdin]=inputdev
27 } 将控制台的标准输入设备,标准输出设备,标注错误输出设备均设为串口设备
21~27行, console_setfile做如下几件事:
1. 如果初始化该设备时注册了device_t.start,即启动设备的函数,则运行该函数,开启该设备
2. 将设备的指针存入stdio_devices[file],这应该是标准输入标准输出、标准出错。
#define stdin 0
#define stdout 1
#define stderr 2
22行将标准出错定为输出设备,这样有错误信息就会通过输出设备打印出来了
28 gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
29 /* Print information */
30 puts ("In: ");
31 if (stdio_devices[stdin] == NULL) {
32 puts ("No input devices available!\n");
33 } else {
34 printf ("%s\n", stdio_devices[stdin]->name);
35 }
36 puts ("Out: ");
37 if (stdio_devices[stdout] == NULL) {
38 puts ("No output devices available!\n");
39 } else {
40 printf ("%s\n", stdio_devices[stdout]->name);
41 }
42 puts ("Err: ");
43 if (stdio_devices[stderr] == NULL) {
44 puts ("No error devices available!\n");
45 } else {
46 printf ("%s\n", stdio_devices[stderr]->name);
47 }
30~47行,将信息打印出来,这里打印出出的信息就为
In: serial
Out: serial
Err: serial
48 /* Setting environment variables */
49 for (i = 0; i < 3; i++) {
50 setenv (stdio_names[i], stdio_devices[i]->name);// 即stdin=serial
51 } stdout = serial
49~51行 将信息写到环境变量中去
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
这样环境变量里 stdin stdout stderr 都为serial
52 return (0);
53 }
---------------------------------------------------------------------------------------------------------------------------------------
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r (); //混杂设备的初始化 很重要
#endif
/* enable exceptions */
enable_interrupts (); //使能中断 即打开cpsr_c的第7位 即外中断,该第7位值为0,表示使能外中断
/* Perform network card initialisation if necessary */
配置几种类型的网卡
#ifdef CONFIG_DRIVER_TI_EMAC
extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
if (getenv ("ethaddr")) {
davinci_eth_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif
#ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
debug ("Reset Ethernet PHY\n");
reset_phy();
#endif
#endif
/*****************************************非常重要**************************************/
/* main_loop() can return to retry autoboot, if so just run it again. */
// for(;;)与while(1) 是一样的,for(;;)编译成汇编后是无条件转移,while(1)是要0和1进行一下比较的,
所以从这个方向上看for(;;)是要比while(1)快的因为少了一个比较指令,但现在的编译器都是有一定的
优化能力的,像while(1)这种会优化成和for(;;)一样的汇编代码
for (;;) {
main_loop ();
//进入主循环 主循环函数处理执行用户命令 main_loop 是在common/main.c中定义和实现的
}
/* NOTREACHED - no way out of command loop except booting */
}
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
#ifdef CONFIG_MODEM_SUPPORT
static inline void mdm_readline(char *buf, int bufsiz);
/* called from main loop (common/main.c) */
extern void dbg(const char *fmt, ...);
int mdm_init (void)
{
char env_str[16];
char *init_str;
int i;
extern char console_buffer[];
extern void enable_putc(void);
extern int hwflow_onoff(int);
enable_putc(); /* enable serial_putc() */
#ifdef CONFIG_HWFLOW
init_str = getenv("mdm_flow_control");
if (init_str && (strcmp(init_str, "rts/cts") == 0))
hwflow_onoff (1);
else
hwflow_onoff(-1);
#endif
for (i = 1;;i++) {
sprintf(env_str, "mdm_init%d", i);
if ((init_str = getenv(env_str)) != NULL) {
serial_puts(init_str);
serial_puts("\n");
for(;;) {
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini%d: [%s]", i, console_buffer);
if ((strcmp(console_buffer, "OK") == 0) ||
(strcmp(console_buffer, "ERROR") == 0)) {
dbg("ini%d: cmd done", i);
break;
} else /* in case we are originating call ... */
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini%d: connect", i);
return 0;
}
}
} else
break; /* no init string - stop modem init */
udelay(100000);
}
udelay(100000);
/* final stage - wait for connect */
for(;i > 1;) { /* if 'i' > 1 - wait for connection
message from modem */
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini_f: [%s]", console_buffer);
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini_f: connected");
return 0;
}
}
return 0;
}
/* 'inline' - We have to do it fast */
static inline void mdm_readline(char *buf, int bufsiz)
{
char c;
char *p;
int n;
n = 0;
p = buf;
for(;;) {
c = serial_getc();
/* dbg("(%c)", c); */
switch(c) {
case '\r':
break;
case '\n':
*p = '\0';
return;
default:
if(n++ > bufsiz) {
*p = '\0';
return; /* sanity check */
}
*p = c;
p++;
break;
}
}
}
#endif /* CONFIG_MODEM_SUPPORT */
以下是main_loop函数的分析 该文件在common/main.c中
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER // CFG_HUSH_PARSER没有定义,所以执行
static char lastcommand[CFG_CBSIZE] = { 0, }; CFG_CBSIZE为256 用于记录console buffer size
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 执行 定义指针s和int型bootdelay。用于uboot判断
无任何按键按下,就执行CONFIG_BOOTCOMMAND宏所对应的命令,这个命令通常用于加载启动操作系统;
char *s;
int bootdelay;
#endif
#ifdef CONFIG_PREBOOT 没定义,不执行
char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT 没定义 不执行
unsigned long bootcount = 0;
unsigned long bootlimit = 0;
char *bcs;
char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO) 没定义 但是我们可以定义,让显示开机log
ulong bmp = 0; /* default bitmap */
extern int trab_vfd (ulong bitmap);
#ifdef CONFIG_MODEM_SUPPORT 没定义
if (do_mdm_init)
bmp = 1; /* alternate bitmap */
#endif
trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */
#ifdef CONFIG_BOOTCOUNT_LIMIT 没定义 不执行
bootcount = bootcount_load();
bootcount++;
bootcount_store (bootcount);
sprintf (bcs_set, "%lu", bootcount);
setenv ("bootcount", bcs_set);
bcs = getenv ("bootlimit");
bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#ifdef CONFIG_MODEM_SUPPORT 暂时不支持通话 没定义 不执行
debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);
if (do_mdm_init) {
char *str = strdup(getenv("mdm_cmd"));
setenv ("preboot", str); /* set or delete definition */
if (str != NULL)
free (str);
mdm_init(); /* wait for modem connection */
}
#endif /* CONFIG_MODEM_SUPPORT */
#ifdef CONFIG_VERSION_VARIABLE 没定义 不执行
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */ 设置环境变量ver=“”
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CFG_HUSH_PARSER 没定义 不执行
u_boot_hush_start ();
#endif
#ifdef CONFIG_AUTO_COMPLETE 该宏非常好用,可以让我们自动补齐命令
install_auto_complete();
#endif
#ifdef CONFIG_PREBOOT 没定义 不执行
if ((p = getenv ("preboot")) != NULL) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (p, 0);
# else
parse_string_outer(p, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
#endif /* CONFIG_PREBOOT */
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay"); //获取环境变量bootdelay的值 即将环境变量bootdelay等号后面的值的地址保存在s ,即s保存字符串的地址,比如是5,即5此时是字符串
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; //如果环境变量中没有bootdelay参数,则就用默认的CONFIG_BOOTDELAY来当作倒数计时
bootdelay保存该环境变量的值
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
# ifdef CONFIG_BOOT_RETRY_TIME 不执行
init_cmd_timeout ();
# endif /* CONFIG_BOOT_RETRY_TIME */
#ifdef CONFIG_POST
if (gd->flags & GD_FLG_POSTFAIL) {
s = getenv("failbootcmd");
}
else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT 不执行
if (bootlimit && (bootcount > bootlimit)) {
printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv ("altbootcmd");
}
else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
*******************************************************************************************************************************************
以下是两种情况
第一种情况是 在bootdelay内不按空格键:s=getenv ("bootcmd");run_command (s, 0);直接启动内核了
第二种情况就是 在bootdelay内按下空格键: s = getenv("menucmd");run_command (s, 0);进入命令循环
len = readline (CFG_PROMPT);run_command (lastcommand, flag);之后在run_command里
if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根据命令的名字
找到该命令表结构体,调用它的cmd参数 最终还是回到do_xxx
比如 run_command("bootm")---->根据“bootm”名字会在命令表里去找到相应的命令表,最终用该命令表的
cmd参数即do_bootm去处理宏U_BOOT_CMD定义了一个段属性为.u_boot_cmd的命令表结构体 struct cmd_tbl_t
s = getenv ("bootcmd"); //启动命令 非常重要 bootcmd = nand read 目的地址 源地址; bootm 目的地址
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //如果倒数计时之内没有按空格键 则直接
run_command (s, 0) 直接启动内核了
# ifdef CONFIG_AUTOBOOT_KEYED 不执行
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (s, 0); //直接启动内核了
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
# ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd"); //否则如果在倒数计时之内按下了空格键,就会跑到这里来
if (s) {
# ifndef CFG_HUSH_PARSER
run_command (s, 0); //就会进入到菜单里面 当然你需要自己去实现这个菜单
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
#ifdef CONFIG_AMIGAONEG3SE
{
extern void video_banner(void);
video_banner();
}
#endif
/*
* Main Loop for Monitor Command Processing //进入循环 处理用户输入的命令
*/
#ifdef CFG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else //执行else分支 在这里进入循环来处理各种用户输入的命令
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
if (rc >= 0) {
/* Saw enough of a valid command to
* restart the timeout.
*/
reset_cmd_timeout();
}
#endif
len = readline (CFG_PROMPT); //读取串口里用户输入的命令 一回车这些输入的字符串就被考入到console_buffer里 len表示获取的字符串长度
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer); //将用户输入的命令字符串拷贝到lastcommand里
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
else if (len == -2) {
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
/* Reinit board to run initialization code again */
do_reset (NULL, 0, 0, NULL);
# else
return; /* retry autoboot */
# endif
}
#endif
if (len == -1)
puts ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag); //解析和处理用户输入的命令
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
#endif /*CFG_HUSH_PARSER*/
}
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
main_loop又臭又长,去掉宏注释掉的部分就只剩下一点点了。如下:
void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_AUTO_COMPLETE
install_auto_complete(); //安装自动补全的函数,分析如下 。
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d/n/n", bootdelay);
s = getenv ("bootcmd"); //获取引导命令。分析见下面。
debug ("### main_loop: bootcmd=/"%s/"/n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {//如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。abortboot函数的分析见下面。
/* 重点1 */
run_command (s, 0); //运行引导内核的命令。这个命令是在配置头文件中定义的。run_command的分析在下面。
}
#endif /* CONFIG_BOOTDELAY */
for (;;) { 否则就进入uboot命令行执行各个uboot命令了
len = readline (CONFIG_SYS_PROMPT); //CONFIG_SYS_PROMPT的意思是回显字符,一般是“>”。这是由配置头文件定义的 readline读入用户输入的字符串,存放在console_buffer
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 == -1)
puts ("<INTERRUPT>/n");
else
rc = run_command (lastcommand, flag); //执行命令 ,重点2。
if (rc <= 0) {//执行失败,则清空记录
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
}
2。自动补全
common/common.c
int var_complete(int argc, char *argv[], char last_char, int maxv, char *cmdv[])
{
static char tmp_buf[512];
int space;
space = last_char == '/0' || last_char == ' ' || last_char == '/t';
if (space && argc == 1)
return env_complete("", maxv, cmdv, sizeof(tmp_buf), tmp_buf);
if (!space && argc == 2)
return env_complete(argv[1], maxv, cmdv, sizeof(tmp_buf), tmp_buf);
return 0;
}
static void install_auto_complete_handler(const char *cmd,
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]))
{
cmd_tbl_t *cmdtp;
cmdtp = find_cmd(cmd);
if (cmdtp == NULL)
return;
cmdtp->complete = complete; //命令结构体的complete指针指向传入的函数。
}
void install_auto_complete(void)
{
#if defined(CONFIG_CMD_EDITENV)
install_auto_complete_handler("editenv", var_complete);
#endif
install_auto_complete_handler("printenv", var_complete);
install_auto_complete_handler("setenv", var_complete);
#if defined(CONFIG_CMD_RUN)
install_auto_complete_handler("run", var_complete);
#endif
}
可以看到将editenv、printenv、setenv和run的自动补全函数安装为 var_complete。
var_complete的功能是根据给出的前缀字符串,找出所有前缀相同的命令。
每个命令在内存中用一个cmd_tbl_t 表示。
include/command.h
struct cmd_tbl_s {
char *name; /* 命令名,输入的就是它 */
int maxargs; /* 最大参数个数 */
int repeatable; /* 允许自动重发,也就是在按下空格键之后执行最后一条命令。 */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 实现命令的参数 */
char *usage; /* 短的提示信息 */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* 详细的帮助信息。 */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
extern cmd_tbl_t __u_boot_cmd_start;
extern cmd_tbl_t __u_boot_cmd_end;
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) / U_BOOT_CMD宏定义了一个cmd_tbl_t结构的uboot命令,用find_cmd(argv[0])) 参数是命令的名字,根据名字找到该命令结构cmd_tbl_t
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
#define U_BOOT_CMD_MKENT(name,maxargs,rep,cmd,usage,help) /
{#name, maxargs, rep, cmd, usage, help}
uboot中的命令使用U_BOOT_CMD这个宏声明来注册进系统,链接脚本会把所有的cmd_tbl_t结构体放在相邻的地方。
链接脚本中的一些内容如下:
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
可见,__u_boot_cmd_start 和__u_boot_cmd_end 分别对应命令结构体在内存中开始和结束的地址。
3。abortboot函数的分析
abortboot是uboot在引导期间的延时函数。期间可以按键进入uboot的命令行。
common/main.c
static __inline__ int abortboot(int bootdelay) 该函数 即在延时时间内,若扫描有键按下即返回1,若无键按下 则返回0
{
int abort = 0;
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#if defined CONFIG_ZERO_BOOTDELAY_CHECK //如果定义了这个宏,即使定义延时为0,也会检查一次是否有按键按下。只要在这里执行之前按键,还是能进入uboot的命令行。
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press */ 测试是否有按键按下
(void) getc(); /* consume input */接收按键值
puts ("/b/b/b 0");
abort = 1; /* don't auto boot */修改标记,停止自动引导
}
}
#endif
while ((bootdelay > 0) && (!abort)) { //如果延时大于零并且停止标记没有赋值则进入延时循环,直到延时完或者接收到了按 键
int i;
--bootdelay;
/* delay 100 * 10ms */ 每秒中测试按键100次,之后延时10ms。
for (i=0; !abort && i<100; ++i) {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */*/修改标记,停止自动引导
bootdelay = 0; /* no more delay */延时归零
(void) getc(); /* consume input */获取按键
break;
}
udelay(10000);//延时10000us,也就是10ms
}
printf("/b/b/b%2d ", bootdelay);//打印当前剩余时间
}
putc('/n');
return abort;//返回结果:1-停止引导,进入命令行; 0-引导内核。
}
可以看到uboot延时的单位是秒,如果想提高延时的精度,比如想进行10ms级的延时,将udelay(10000)改为udelay(100)就可以了 。
run_command 重点
//
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CONFIG_SYS_CBSIZE];
char *str = cmdbuf;
char *argv[CONFIG_SYS_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) >= CONFIG_SYS_CBSIZE) { //命令太长
puts ("## Command too long!/n");
return -1;
}
strcpy (cmdbuf, cmd); //将命令拷贝到临时命令缓冲cmdbuf
/* Process separators and check for invalid
* repeatable commands
*/
while (*str) { //str指向cmdbuf
/*
* 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;
}
/*
* Limit the token to data between separators
*/
token = str; //token指向命令的开头
if (*sep) { //如果是分隔符的话,将分隔符替换为空字符
str = sep + 1; /* start of command for next pass */str指向下一句的开头
*sep = '/0';
}
else
str = sep; /* no more commands for next pass */如果没有其它命令了,str指向命令尾部
/* find macros in this token and replace them */
process_macros (token, finaltoken);
//将token指向的命令中的宏替换掉,如把$(kernelsize)替换成内核的大小
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
//将每一个参数用‘/0’隔开,argv中的每一个指针指向一个参数的起始地址。 返回值为参数的个数
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) { //检查参数个数是否过多
cmd_usage(cmdtp);
rc = -1;
continue;
}
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {//调用命令执行函数。这是最重要的一步。
rc = -1;
}
repeatable &= cmdtp->repeatable;
//设置命令自动重复执行的标志。也就是只按下enter键是否可以执行最近执行的命令 .
/* Did the user stop this? */
if (had_ctrlc ())
//检查是否有ctrl+c按键按下,如果有,结束当前命令。本函数并没有从中断接收字符,接收ctrl+c的是一些执行命令的函数。
return -1; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
---------------------------------------------------------------------------------------------------
其实 main_loop函数就是处理两个run_command函数,一个是在开机延迟时间内若没有按下任意键,则
直接启动内核(bootcmd);一个是若按下了,则不会启动内核,处理uboot命令
uboot最核心的东西就是命令,关于uboot命令的章节笔记本上都有,此处略。
现在来看uboot的最后一部分 启动内核
启动内核 s=getenv ("bootcmd");run_command (s, 0);
环境变量bootcmd = nand read 目的地址 源地址 大小; bootm 目的地址
只要是启动内核,就一定要用到bootcmd,执行这两条指令
nand read指令的实现是在cmd_nand.c文件中的do_nand函数中实现的,do_nand函数中有很多关于nand命令
比如 nand write,nand erase等等
***************************************************************************************************************
我们来看下关于nand相关的指令实现的函数 do_nand
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
int i, dev, ret = 0;
ulong addr, off;
size_t size;
char *cmd, *s;
nand_info_t *nand;
#ifdef CFG_NAND_QUIET
int quiet = CFG_NAND_QUIET;
#else
int quiet = 0;
#endif
const char *quiet_str = getenv("quiet");
/* at least two arguments please */
if (argc < 2) //r如果参数的个数小于2个 退出,也即参数的个数至少是两个 如nand read
goto usage;
if (quiet_str)
quiet = simple_strtoul(quiet_str, NULL, 0) != 0;
cmd = argv[1]; //获取nand 后面的那一个命令
if (strcmp(cmd, "info") == 0) { //命令 nand info
putc('\n');
for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {
if (nand_info[i].name)
printf("Device %d: %s, sector size %u KiB\n",
i, nand_info[i].name,
nand_info[i].erasesize >> 10);
}
return 0;
}
if (strcmp(cmd, "device") == 0) { // 命令 nand device
if (argc < 3) {
if ((nand_curr_device < 0) ||
(nand_curr_device >= CFG_MAX_NAND_DEVICE))
puts("\nno devices available\n");
else
printf("\nDevice %d: %s\n", nand_curr_device,
nand_info[nand_curr_device].name);
return 0;
}
dev = (int)simple_strtoul(argv[2], NULL, 10);
if (dev < 0 || dev >= CFG_MAX_NAND_DEVICE || !nand_info[dev].name) {
puts("No such device\n");
return 1;
}
printf("Device %d: %s", dev, nand_info[dev].name);
puts("... is now current device\n");
nand_curr_device = dev;
#ifdef CFG_NAND_SELECT_DEVICE
/*
* Select the chip in the board/cpu specific driver
*/
board_nand_select_device(nand_info[dev].priv, dev);
#endif
return 0;
}
if (strcmp(cmd, "bad") != 0 && strcmp(cmd, "erase") != 0 &&
strncmp(cmd, "dump", 4) != 0 &&
strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 &&
strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 &&
strcmp(cmd, "biterr") != 0 &&
strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 )
goto usage;
/* the following commands operate on the current device */
if (nand_curr_device < 0 || nand_curr_device >= CFG_MAX_NAND_DEVICE ||
!nand_info[nand_curr_device].name) {
puts("\nno devices available\n");
return 1;
}
nand = &nand_info[nand_curr_device];
if (strcmp(cmd, "bad") == 0) {
printf("\nDevice %d bad blocks:\n", nand_curr_device);
for (off = 0; off < nand->size; off += nand->erasesize)
if (nand_block_isbad(nand, off))
printf(" %08lx\n", off);
return 0;
}
/*
* Syntax is:
* 0 1 2 3 4
* nand erase [clean] [off size]
*/
if (strcmp(cmd, "erase") == 0 || strcmp(cmd, "scrub") == 0) {
nand_erase_options_t opts;
/* "clean" at index 2 means request to write cleanmarker */
int clean = argc > 2 && !strcmp("clean", argv[2]);
int o = clean ? 3 : 2;
int scrub = !strcmp(cmd, "scrub");
printf("\nNAND %s: ", scrub ? "scrub" : "erase");
/* skip first two or three arguments, look for offset and size */
if (arg_off_size(argc - o, argv + o, nand, &off, &size) != 0)
return 1;
memset(&opts, 0, sizeof(opts));
opts.offset = off;
opts.length = size;
opts.jffs2 = clean;
opts.quiet = quiet;
if (scrub) {
puts("Warning: "
"scrub option will erase all factory set "
"bad blocks!\n"
" "
"There is no reliable way to recover them.\n"
" "
"Use this command only for testing purposes "
"if you\n"
" "
"are sure of what you are doing!\n"
"\nReally scrub this NAND flash? <y/N>\n");
if (getc() == 'y' && getc() == '\r') {
opts.scrub = 1;
} else {
puts("scrub aborted\n");
return -1;
}
}
ret = nand_erase_opts(nand, &opts); //真正解析该命令的函数
printf("%s\n", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
if (strncmp(cmd, "dump", 4) == 0) {
if (argc < 3)
goto usage;
s = strchr(cmd, '.');
off = (int)simple_strtoul(argv[2], NULL, 16);
if (s != NULL && strcmp(s, ".oob") == 0)
ret = nand_dump(nand, off, 1);
else
ret = nand_dump(nand, off, 0);
return ret == 0 ? 1 : 0;
}
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
//命令是 nand read 和nand write 命令的解析
int read;
if (argc < 4)
goto usage;
addr = (ulong)simple_strtoul(argv[2], NULL, 16);
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
printf("\nNAND %s: ", read ? "read" : "write");
if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
return 1;
s = strchr(cmd, '.');
if (!s || !strcmp(s, ".jffs2") ||
!strcmp(s, ".e") || !strcmp(s, ".i")) {
if (read)
ret = nand_read_skip_bad(nand, off, &size,
(u_char *)addr);
else
ret = nand_write_skip_bad(nand, off, &size,
(u_char *)addr);
} else if (s != NULL && !strcmp(s, ".oob")) {
/* out-of-band data */
mtd_oob_ops_t ops = {
.oobbuf = (u8 *)addr,
.ooblen = size,
.mode = MTD_OOB_RAW
};
if (read)
ret = nand->read_oob(nand, off, &ops);
else
ret = nand->write_oob(nand, off, &ops);
} else {
printf("Unknown nand command suffix '%s'.\n", s);
return 1;
}
printf(" %d bytes %s: %s\n", size,
read ? "read" : "written", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
if (strcmp(cmd, "markbad") == 0) {
addr = (ulong)simple_strtoul(argv[2], NULL, 16);
int ret = nand->block_markbad(nand, addr);
if (ret == 0) {
printf("block 0x%08lx successfully marked as bad\n",
(ulong) addr);
return 0;
} else {
printf("block 0x%08lx NOT marked as bad! ERROR %d\n",
(ulong) addr, ret);
}
return 1;
}
if (strcmp(cmd, "biterr") == 0) {
/* todo */
return 1;
}
if (strcmp(cmd, "lock") == 0) {
int tight = 0;
int status = 0;
if (argc == 3) {
if (!strcmp("tight", argv[2]))
tight = 1;
if (!strcmp("status", argv[2]))
status = 1;
}
/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
if (status) {
ulong block_start = 0;
ulong off;
int last_status = -1;
struct nand_chip *nand_chip = nand->priv;
/* check the WP bit */
nand_chip->cmdfunc (nand, NAND_CMD_STATUS, -1, -1);
printf("device is %swrite protected\n",
(nand_chip->read_byte(nand) & 0x80 ?
"NOT " : ""));
for (off = 0; off < nand->size; off += nand->writesize) {
int s = nand_get_lock_status(nand, off);
/* print message only if status has changed
* or at end of chip
*/
if (off == nand->size - nand->writesize
|| (s != last_status && off != 0)) {
printf("%08lx - %08lx: %8d pages %s%s%s\n",
block_start,
off-1,
(off-block_start)/nand->writesize,
((last_status & NAND_LOCK_STATUS_TIGHT) ? "TIGHT " : ""),
((last_status & NAND_LOCK_STATUS_LOCK) ? "LOCK " : ""),
((last_status & NAND_LOCK_STATUS_UNLOCK) ? "UNLOCK " : ""));
}
last_status = s;
}
} else {
if (!nand_lock(nand, tight)) {
puts("NAND flash successfully locked\n");
} else {
puts("Error locking NAND flash\n");
return 1;
}
}
#endif
return 0;
}
if (strcmp(cmd, "unlock") == 0) {
if (arg_off_size(argc - 2, argv + 2, nand, &off, &size) < 0)
return 1;
/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
if (!nand_unlock(nand, off, size)) {
puts("NAND flash successfully unlocked\n");
} else {
puts("Error unlocking NAND flash, "
"write and erase will probably fail\n");
return 1;
}
#endif
return 0;
}
usage:
printf("Usage:\n%s\n", cmdtp->usage);
return 1;
}
**********************************************************************************************************************************
bootm命令
bootm命令的实现
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
…… ……
/* 检查头部 */
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
…… ……
/*解压缩*/
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); //将真正的内核移动到加载地址处hdr->ih_load
#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;
}
}
…… …… ……
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX: //惊奇!!!!!!!!!!!!!!!!!!!在这里调用了do_bootm_linux函数
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;
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
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;
}
bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。
bootm命令主要做三件事情:
1,解析uImage头部信息,获取image_header结构体
2,将真正的内核移动到加载地址处,如果相等就不用移动
3,根据操作系统,选择启动内核的函数,如do_bootm_linux函数
3.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)
{
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
… … … …
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
… … … …
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); /*启动内核,传递启动参数*/
}
do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。
U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。
以下是bootm.c里的do_bootm_linux的源码
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number; //机器ID
void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs"); //获取启动参数的命令行
#endif
theKernel = (void (*)(int, int, uint))images->ep; // the_kernel即镜像文件的入口地址
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
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); //设置内存TAG,会用到bd->bi_dram[].start bd->bi_dram[].size
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
/* 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 ();
theKernel (0, machid, bd->bi_boot_params); //启动内核,一去不复返
/* does not return */
return 1;
}
第二阶段小结: bootcmd
主要是3个关键函数 start_armboot ----> main_loop ------> do_bootm_linux
当我们在倒数计时时按下空格键进入uboot命令界面,我们若输入boot命令回车,便会调用bootcmd环境变量的两条指令,同样启动内核
我们按下空格时出现的菜单 是需要自己去实现的,在common里创建一个新文件cmd_menu.c里去实现
mkimage使用详解
###############################################################################################################################
mkimage使用详解(转载) uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
root@Glym:/tftpboot# ./mkimage
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
参数说明:
-A 指定CPU的体系结构:
取值 表示的体系结构
alpha Alpha
arm A RM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64 Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64 Bit
m68k MC68000
-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem
-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式
-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映象名
-d 指定制作映象的源文件
U-BOOT下使用bootm引导内核方法
一、在开始之前先说明一下bootm相关的东西。
1、 首先说明一下,S3C2410架构下的bootm只对sdram中的内核镜像文件进行操作(好像AT91架构提供了一段从flash复制内核镜像的代码, 不过针对s3c2410架构就没有这段代码,虽然可以在u-boot下添加这段代码,不过好像这个用处不大),所以请确保你的内核镜像下载到sdram 中,或者在bootcmd下把flash中的内核镜像复制到sdram中。
2、-a参数后是内核的运行地址,-e参数后是入口地址。
3、 在这里,有个非常关键的问题,对于uboot启动内核,我们经常用bootm命令来启动内核,bootm->do_bootm->do_bootm_linux->set_xxx_tag,然后the_kernel
1)如果我们没用mkimage对内核进行处理的话,那直接把内核下载到0x30008000再运行就行,内核会自解压运行(不过内核运行需要一个tag来传递参数,而这个tag建议是由bootloader提供的,在u-boot下默认是由bootm命令建立的)。
!!!!! 要紧记这两种情况
2)如果使用mkimage生成内核镜像文件的话,会在内核的前头加上了64byte的信息,供建立tag之用。bootm命令会首先判断bootm xxxx 这个指定的地址xxxx是否与-a指定的加载地址相同。
(1)如果不同的话会从这个地址开始提取出这个64byte的头部,对其进行分析,然后把去掉头部的内核复制到-a指定的load地址中去运行之
(2)如果相同的话那就让其原封不同的放在那,但-e指定的入口地址会推后64byte,以跳过这64byte的头部。
如果启动地址与内核镜像的加载地址相同,那么镜像就不用移动,但是镜像的入口地址必须后移64个字节
如果启动地址与内核镜像的加载地址不同,那么镜像需要移动,此时,要将去掉64字节的头部的镜像移动到加载地址处
二、好,接着介绍使用mkimage生成镜像文件并下载运行的方法。
方法一、
1、首先,用u-boot/tools/mkimage这个工具为你的内核加上u-boot引导所需要的文件头,具体做法如下:
[root@localhost tftpboot]#mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage zImage.img // -d zImage 表示原始映像文件 zImage.img 表示生成的最终镜像文件
Image Name: linux-2.6.14
Created: Fri Jan 12 17:14:50 2007
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1262504 Bytes = 1232.91 kB = 1.20 MB
Load Address: 0x30008000
Entry Point: 0x30008000
这里解释一下参数的意义:
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
2 、下载内核
U-Boot 1.1.3 (Jan 12 2007 - 16:16:36) 经计算uboot镜像的大小为127K左右
U-Boot code: 33F80000 -> 33F9BAC0 BSS: -> 33F9FBAC 在这里调用init_senqunce函数序列中的display_banner,分别打印的是 printf ("\n\n%s\n\n", version_string);
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
RAM Configuration: 在这里调用init_senqunce函数序列中disply_dram_config函数
Bank #0: 30000000 64 MB 打印是内存的起始物理地址和大小
Nor Flash: 512 kB nor flash的初始化
Nand Flash: 64 MB 调用nand flash的初始化
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
sbc2410=>tftp 0x31000000 zImage.img
TFTP from server 192.168.1.115; our IP address is 192.168.1.128
Filename 'zImage.img'.
Load address: 0x31000000
Loading: #################################################################
#################################################################
#################################################################
####################################################
done
Bytes transferred = 1263324 (1346dc hex)
3.运行
sbc2410=>bootm 0x31000000
## Booting image at 31000000 ...
Image Name: linun-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1263260 Bytes = 1.2 MB
Load Address: 30008000
Entry Point: 30008000
Verifying Checksum ... OK
OK
Starting kernel ...
Uncompressing Linux.............................................................Linux version 2.6.14 (root@luofuchong) (gcc version 3.4.1) #21 Fri Oct 20 17:206CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T)
Machine: SMDK2410
Memory policy: ECC disabled, Data cache writeback
CPU S3C2410A (id 0x32410002)
S3C2410: core 202.800 MHz, memory 101.400 MHz, peripheral 50.700 MHz
S3C2410 Clocks, (c) 2004 Simtec Electronics
CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on
USB Control, (c) 2006 sbc2410
CPU0: D VIVT write-back cache
CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
Built 1 zonelists
Kernel command line: console="ttySAC0" root="/dev/nfs" nfsroot="192".168.1.115:/frien"irq: clearing subpending status 00000002
PID hash table entries: 512 (order: 9, 8192 bytes)
timer tcon="00500000", tcnt a509, tcfg 00000200,00000000, usec 00001e4c
Console: colour dummy device 80x30
Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
Memory: 64MB = 64MB total
Memory: 62208KB available (1924K code, 529K data, 108K init)
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
softlockup thread 0 started up.
NET: Registered protocol family 16
S3C2410: Initialising architecture
SCSI subsystem initialized
usbcore: registered new driver usbfs
usbcore: registered new driver hub
S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics
DMA channel 0 at c4800000, irq 33
DMA channel 1 at c4800040, irq 34
DMA channel 2 at c4800080, irq 35
DMA channel 3 at c48000c0, irq 36
NetWinder Floating Point Emulator V0.97 (double precision)
devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au)
devfs: devfs_debug: 0x0
devfs: boot_options: 0x1
yaffs Oct 18 2006 12:39:51 Installing.
Console: switching to colour frame buffer device 30x40
fb0: s3c2410fb frame buffer device
fb1: Virtual frame buffer device, using 1024K of video memory
led driver initialized
s3c2410 buttons successfully loaded
s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410
s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410
s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410
io scheduler noop registered
io scheduler anticipatory registered
io scheduler deadline registered
io scheduler cfq registered
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
usbcore: registered new driver ub
Cirrus Logic CS8900A driver for Linux (Modified for SMDK2410)
eth0: CS8900A rev E at 0xe0000300 irq="53", no eeprom , addr: 08: 0:3E:26:0A:5B
S3C24XX NAND Driver, (c) 2004 Simtec Electronics
s3c2410-nand: mapped registers at c4980000
s3c2410-nand: timing: Tacls 10ns, Twrph0 30ns, Twrph1 10ns
NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bi)Scanning device for bad blocks
Bad eraseblock 1884 at 0x01d70000
Creating 4 MTD partitions on "NAND 64MiB 3,3V 8-bit":
0x00000000-0x00020000 : "vivi"
0x00020000-0x00030000 : "param"
0x00030000-0x00200000 : "kernel"
0x00200000-0x04000000 : "root"
usbmon: debugfs is not available
s3c2410-ohci s3c2410-ohci: S3C24XX OHCI
s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1
s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
Initializing USB Mass Storage driver...
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
usbcore: registered new driver usbmouse
drivers/usb/input/usbmouse.c: v1.6:USB HID Boot Protocol mouse driver
mice: PS/2 mouse device common for all mice
s3c2410 TouchScreen successfully loaded
UDA1341 audio driver initialized
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 4096 (order: 2, 16384 bytes)
TCP bind hash table entries: 4096 (order: 2, 16384 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP reno registered
TCP bic registered
NET: Registered protocol family 1
IP-Config: Complete:
device=eth0, addr="192".168.1.128, mask="255".255.255.0, gw="192".168.1.1,
host="luofuchong", domain=, nis-domain=(none),
bootserver="192".168.1.1, rootserver="192".168.1.115, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.115
Looking up port of RPC 100005/1 on 192.168.1.115
VFS: Mounted root (nfs filesystem).
Mounted devfs on /dev
Freeing init memory: 108K
init started: BusyBox v1.1.3 (2006.09.20-14:52+0000) multi-call binary
Starting pid 696, console /dev/tts/0: '/etc/init.d/rcS'
Please press Enter to activate this console.
方法二、
1、首先,用u-boot/tools/mkimage这个工具为你的内核加上u-boot引导所需要的文件头,具体做法如下:
[root@localhost tftpboot]#mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage zImage.img
Image Name: linux-2.6.14
Created: Fri Jan 12 17:14:50 2007
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1262504 Bytes = 1232.91 kB = 1.20 MB
Load Address: 0x30008000
Entry Point: 0x30008040
2 、下载内核
U-Boot 1.1.3 (Jan 12 2007 - 16:16:36)
U-Boot code: 33F80000 -> 33F9BAC0 BSS: -> 33F9FBAC
RAM Configuration:
Bank #0: 30000000 64 MB
Nor Flash: 512 kB
Nand Flash: 64 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
sbc2410=>tftp 0x30008000 zImage.img
TFTP from server 192.168.1.115; our IP address is 192.168.1.128
Filename 'zImage.img'.
Load address: 0x30008000
Loading: #################################################################
#################################################################
#################################################################
####################################################
done
Bytes transferred = 1263324 (1346dc hex)
3.运行
sbc2410=>bootm 0x30008000
## Booting image at 30008000 ...
Image Name: linux-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1261056 Bytes = 1.2 MB
Load Address: 30008000
Entry Point: 30008040
Verifying Checksum ... OK
XIP Kernel Image ... OK
============================================================对于uboot两阶段的小结,很重要!!!==================================
第一阶段:
在脚本uboot.lds中 ENTRY(_start) _start的地址一般是TEXT_BASE
设置异常向量表
进入svc管理模式 arm状态
关看门狗 关中断 时钟初始化
cpu初始化(关mmu 关数据和指令缓存 cpu速率)和内存初始化 在函数cpu_init_crit中完成
重定位
设置栈
清bss断
跳入到start_armboot函数
第二阶段 就进入到lib_arm/board.c中的start_kernel函数
分配gd指针指向的空间和gd->bd指针指向的空间
执行init_sequence函数序列,其实主要目的是初始化并填充gd和gd->bd结构体
分配堆空间mem_malloc_init ,这样才可以初始化环境变量,因为环境变量是要从nand拷贝到内存中的堆空间里
nand初始化 nand_init,因为现在普及采用nand,若是用nor的话,在之前已经初始化了 flash_init函数
环境变量初始化 env_reloacate 有四个很重要的环境变量参数:bootdelay(启动延时),bootcmd(启动命令),menucmd(菜单),bootargs(启动参数,也即最原始的命令行参数)
串口设置的5个函数,这样就能看到串口打印数据
混杂设备 misc_init_r函数
进入循环执行 main_loop函数,处理启动命令或者用户输入的命令 该函数是在common/main.c中
第一种情况是 在bootdelay内不按空格键:s=getenv ("bootcmd");run_command (s, 0);直接启动内核了
第二种轻狂就是 在bootdelay内按下空格键进入menu菜单里: s = getenv("menucmd");run_command (s, 0);然后进入命令循环获取用户从串口里打印的字符 len = readline (CFG_PROMPT);run_command (lastcommand, flag); 之后在run_command里if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根据 命令的名字 找到该命令表结构体,调用它的cmd参数 最终还是回到do_xxx run_command就是来解析命令的
run_command分别解析nand命令和bootm命令,nand命令负责把linux内核读到内存中,bootm负责去启动内核镜像,调用do_bootm----->do_bootm_linux,启动内核
#include <config.h>
#include <version.h>
#include <status_led.h>
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
.globl _start //定义一个全局变量或者函数,该处指的是函数。
_start: b start_code 复位向量 0x0 //定义的各种异常向量表,为每种异常指定了一个地址
ldr pc, _undefined_instruction 未定义指令异常向量 0x4 //当异常到来时,就跑到该异常向量地址出去执行
ldr pc, _software_interrupt 软中断向量 0x8
ldr pc, _prefetch_abort 欲取指令异常向量 0xc
ldr pc, _data_abort 数据操作异常向量 0x10
ldr pc, _not_used 未使用 0x14
ldr pc, _irq 外中断异常向量 0x18
ldr pc, _fiq 快中断异常向量 0x1c
_undefined_instruction: .word undefined_instruction 赋值操作 以下同,即_undefined_instruction = 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 //地址对齐
//当处理器碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序
继续执行。 相应的处理函数在最下面。arm有7种运行模式,当进入某一种异常模式的时候,比如是中断模式。
1 将r0—r12寄存器的值保存在该异常模式的栈里,(事先之前就要将该模式下的栈设置好,放在r13)
2 异常模式下的r14保存用户模式下即将执行指令的地址,为当前PC值加4或者加8
3 cpsr保存在该异常模式下的spsr里
4 cpsr的工作模式被设为这个异常工作模式 比如由 10000 --》 10011
5 PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中,比如 pc= 0x18
当异常处理完毕后,ARM会执行以下几步操作从异常返回:
(1)将连接寄存器LR的值减去相应的偏移量后送到PC中
(2) 将SPSR复制回CPSR中
(3) 若在进入异常处理时设置了中断禁止位,要在此清除
/*
*************************************************************************
*
* Startup Code (called from the ARM reset exception vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
*/
//以下是保存变量的数据区
_TEXT_BASE: //赋值,将uboot在内存的起始基地址TEXT_BASE赋值给_TEXT_BASE
.word TEXT_BASE
.globl _armboot_start //定义一个全局函数变量_armboot_start,将_start的值赋给_srmboot_start ,
_armboot_start: 无论是nor flash启动 nand flash启动还是内存启动_start的值为TEXT_BASE
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start //_bss_start=_bss_start(链接脚本的地址)
_bss_start:
.word __bss_start
.globl _bss_end // _bss_end = _end(链接脚本的地址)
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START //IRQ_STACK_START = 0x0badc0de
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START //FIQ_STACK_START = 0x0badc0de
FIQ_STACK_START:
.word 0x0badc0de
#endif
//保存变量的数据区完毕
/*
* the actual start code
*/
start_code: 一上电复位,便跑到这里
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr //设置cpsr_c的那个字节,设置成1101 0011 即屏蔽外中断 屏蔽快中断 arm状态 svc管理模式 即超级保护模式,可以访问被保护的资源
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
bl coloured_LED_init //在原始的uboot源码中是没有此处的
bl red_LED_on
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) || defined(CONFIG_AT91RM9200DF)
/*
* relocate exception table
*/
ldr r0, =_start //暂不清楚 应该是平台不同的限制
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
//关闭看门狗 关闭所有中断 初始化始终 都是通过设置相关的控制寄存器
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
ldr r0, =pWTCON //向看门寄存器写0 即关闭看门狗
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!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
//关闭mmu 屏蔽指令缓存和数据缓存 设置cpu运行速率和时钟初始化 ram的初始化,下面有分析!!!
bl cpu_init_crit
#endif
#ifndef CONFIG_AT91RM9200
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
//在重定位之前,一定要先初始化ram
//以下是重定位,即将uboot源码从flash上拷贝到sdram上运行,此处考虑的是nor型flash,没有考虑nand型flash
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */ //adr指令是读入代码当前运行位置,绝对位置 也即此时此刻的位置,非常非常重要
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ 若此时是在nor flash进行,则-start的值为0x0
cmp r0, r1 //如果ro=r1 即说明uboot是通过仿真器直接烧入到内存中,直接从内存启动 否则则说明是从flash运行的 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //_armboot_start存放的是_start的值,此是是_TEXT_BASE
ldr r3, _bss_start //此处也是绝对地址 他们都是链接脚本u-boot.lds中定义的
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] */ 从源地址[r0]读取32个字节到寄存器,并更新r0
stmia r1!, {r3-r10} /* copy to target address [r1] */ 拷贝寄存器r3-r10的32个字节值保存到[r1]指明的地址,并更新r1的值
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
// 重定位完毕
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
#endif
/* Set up the stack */
我们将uboot拷贝到内存之后,就要设置栈
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 */
// TEXT_BASE- CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - CONFIG_STACKSIZE_IRQ - CONFIG_STACKSIZE_FIQ -12
即内存基地址分别减去 堆空间 gd_t gd空间 外中断设置的栈空间 快中断设置的栈空间
//清bss段
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
//到此进行跳转到内存里的uboot第二阶段 终于要执行C语言入口函数了start_armboot,重要!!!
ldr pc, _start_armboot /*lib_arm/board.c 文件中 */
_start_armboot: .word start_armboot
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
cpu_init_crit
1、关闭MMU和CPU 内部指令/数据 (I/D)cache。
2、设置CPU 的速度和时钟频率。
3 、RAM 初始化。
#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 */
/*
* 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
/*
* 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
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) || defined(CONFIG_AT91RM9200DF)
#else
bl lowlevel_init //ram的初始化 就是配置13个寄存器的值 在lowlevel_init.S
#endif
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)
sub r2, r2, #(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 r7, sp, #S_PC
stmdb r7, {sp, lr}^ @ Calling SP, LR
str lr, [r7, #0] @ Save calling PC
mrs r6, spsr
str r6, [r7, #4] @ Save CPSR
str r0, [r7, #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)
sub r13, r13, #(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 //以下是每种的异常处理
*/
.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
第一阶段分析小结:
设置异常向量表
进入svc管理模式 arm状态
关看门狗 关中断 时钟初始化
cpu初始化(关mmu 关数据和指令缓存 cpu速率)和内存初始化
//关闭mmu 屏蔽指令缓存和数据缓存 设置cpu运行速率和时钟初始化 ram的初始化
重定位
设置栈
清bss断
跳入到start_armboot函数 ,lib_arm/board.c 中
在这里严重关注: 我们有两种启动方式,nand启动和nor启动,我们上面的重定位是nor型
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */ //adr指令是读入代码的当前位置,绝对位置 也即此时此刻的位置,非常非常重要
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1
//如果ro=r1 即说明uboot是通过仿真器直接烧入到内存中,直接从内存启动
否则则说明是从flash运行的 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start //_armboot_start存放的是_start的值,是绝对地址
ldr r3, _bss_start //此处也是绝对地址 他们都是链接脚本u-boot.lds中定义的
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] */ 从源地址[r0]读取32个字节到寄存器,并更新r0
stmia r1!, {r3-r10} /* copy to target address [r1] */ 拷贝寄存器r3-r10的32个字节值保存到[r1]指明的地址,并更新r1的值
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
对于nand启动,是首先我们nand 4k内容被copy至内部4k ram里,执行,然后执行到重定位时,
从nand上拷贝uboot镜像到SDRAM的33f80000处
//#define S3C2440_NAND_BASE 0x4E000000
//取得Nand Flash设置寄存器的地址
mov r1, #S3C2440_NAND_BASE
//将R2设为0xFFF0
ldr r2, =0xfff0 // initial value tacls=3,rph0=7,rph1=7
//#define oNFCONF 0x00
//读取Nand Flash设置寄存器中的值到R3中
ldr r3, [r1, #oNFCONF]
//将R3或上R2后保存到R3中
orr r3, r3, r2
//将R3中的值保存到Nand Flash设置寄存器中
//TWRPH0 - 111 - Duration = HCLK * (TWRPH0 + 1)
//TACLS - 11 - Duration = HCLK * TACLS
str r3, [r1, #oNFCONF]
//#define oNFCONT 0x04
//读取Nand Flash控制寄存器中的值到R3中
ldr r3, [r1, #oNFCONT]
//将R3的[0]位置1
orr r3, r3, #1 // enable nand controller
//将R3中的值保存到Nand Flash控制寄存器中
//Mode - 1:Nand Flash Controller Enable
str r3, [r1, #oNFCONT]
//读取虚拟起始地址到R0中
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
//预留malloc所需要的空间
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
//预留bdinfo所需要的空间
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
//预留中断和快速中断向量表空间
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//预留12字节给中断栈
sub sp, r0, #12 /* leave 3 words for abort-stack */
// copy u-boot to RAM
//读取虚拟起始地址到R0中,作为目标地址
ldr r0, _TEXT_BASE
//将R1设为0,作为源地址
mov r1, #0x0
//将UBOOT大小的值保存在R2中,作为数据大小
mov r2, #CFG_UBOOT_SIZE
//跳转到nand_read_ll处执行
//并将下一条指令的地址保存在LR中
bl nand_read_ll
nand_read_ll的原型为
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
之前设置的R0 R1 R2为它的3个参数
/************************************************************************************/
/**************************** 开始第二阶段分析 **************************************/
/************************************************************************************/
开始第二阶段
第二阶段是在lib_arm/board.c bootm.c两个文件
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。
看board.c文件
#include <common.h>
#include <command.h>
#include <malloc.h>
#include <devices.h>
#include <version.h>
#include <net.h>
#include <serial.h>
#include <nand.h>
#include <onenand_uboot.h>
#ifdef CONFIG_DRIVER_SMC91111
#include "../drivers/net/smc91111.h"
#endif
#ifdef CONFIG_DRIVER_LAN91C96
#include "../drivers/net/lan91c96.h"
#endif
DECLARE_GLOBAL_DATA_PTR;
ulong monitor_flash_len;
#ifdef CONFIG_HAS_DATAFLASH
extern int AT91F_DataflashInit(void);
extern void dataflash_print_info(void);
#endif
#ifndef CONFIG_IDENT_STRING
#define CONFIG_IDENT_STRING ""
#endif
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
#ifdef CONFIG_DRIVER_CS8900
extern void cs8900_get_enetaddr (uchar * addr);
#endif
#ifdef CONFIG_DRIVER_RTL8019
extern void rtl8019_get_enetaddr (uchar * addr);
#endif
#if defined(CONFIG_HARD_I2C) || \
defined(CONFIG_SOFT_I2C)
#include <i2c.h>
#endif
/*
* Begin and End of memory area for malloc(), and current "brk"
*/
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;
static
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);
}
void *sbrk (ptrdiff_t increment)
{
ulong old = mem_malloc_brk;
ulong new = old + increment;
if ((new < mem_malloc_start) || (new > mem_malloc_end)) {
return (NULL);
}
mem_malloc_brk = new;
return ((void *) old);
}
/************************************************************************
* Coloured LED functionality
************************************************************************
* May be supplied by boards if desired
*/
void inline __coloured_LED_init (void) {}
void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
void inline __red_LED_on (void) {}
void inline red_LED_on (void) __attribute__((weak, alias("__red_LED_on")));
void inline __red_LED_off(void) {}
void inline red_LED_off(void) __attribute__((weak, alias("__red_LED_off")));
void inline __green_LED_on(void) {}
void inline green_LED_on(void) __attribute__((weak, alias("__green_LED_on")));
void inline __green_LED_off(void) {}
void inline green_LED_off(void)__attribute__((weak, alias("__green_LED_off")));
void inline __yellow_LED_on(void) {}
void inline yellow_LED_on(void)__attribute__((weak, alias("__yellow_LED_on")));
void inline __yellow_LED_off(void) {}
void inline yellow_LED_off(void)__attribute__((weak, alias("__yellow_LED_off")));
/************************************************************************
* Init Utilities *
************************************************************************
* Some of this code should be moved into the core functions,
* or dropped completely,
* but let's get it working (again) first...
*/
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);
}
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);
#ifdef CONFIG_MODEM_SUPPORT
debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
return (0);
}
/*
* WARNING: this code looks "cleaner" than the PowerPC version, but
* has the disadvantage that you either get nothing, or everything.
* On PowerPC, you might see "DRAM: " before the system hangs - which
* gives a simple yet clear indication which part of the
* initialization if failing.
*/
static int display_dram_config (void) //打印显示ram的配置信息
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i<CONFIG_NR_DRAM_BANKS; 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<CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
puts ("Flash: ");
print_size (size, "\n");
}
#endif /* CFG_NO_FLASH */
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
static int init_func_i2c (void)
{
puts ("I2C: ");
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
puts ("ready\n");
return (0);
}
#endif
/*
* Breathe some life into the board...
*
* Initialize a serial port as console, and carry out some hardware
* tests.
*
* The first part of initialization is running from Flash memory;
* its main purpose is to initialize the RAM so that we
* can relocate the monitor code to RAM.
*/
/*
* All attempts to come up with a "common" initialization sequence
* that works for all boards and architectures failed: some of the
* requirements are just _too_ different. To get rid of the resulting
* mess of board dependent #ifdef'ed code we now make the whole
* initialization sequence configurable to the user.
*
* The requirements for any new initalization function is simple: it
* receives a pointer to the "global data" structure as it's only
* argument, and returns an integer return code, where 0 means
* "continue" and != 0 means "fatal error, hang the system".
*/
typedef int (init_fnc_t) (void);
int print_cpuinfo (void); /* test-only */
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 */ 打印uboot相关信息
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
//配置可用的ram,非常重要,在这里开始进行配置DRAM信息,用到结构体bd->bi_dram[BANK_NR].start
bd->bi_dram[BANK_NR].size
display_dram_config, //打印显示ram的配置信息
NULL,
};
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/cmd_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,
};
void start_armboot (void) //start_armboot函数 非常重要,c function第一个函数!!!
{
init_fnc_t **init_fnc_ptr;
char *s;
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
//在重定位之后,即uboot的代码从flash拷到sdram 此时连接脚本里的_start 等于TEXT_BASE
/* Pointer is writable since we allocated a register for it */
// 给全局变量gd分配空间大小且指定gd的位置 这里gd是一个结构体,在uboot内存分布中
是CFG_GBL_DATA_SIZE一共128字节
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t)); //gd指针所指向的空间清零
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //给gd中的bd指针分配空间大小
memset (gd->bd, 0, sizeof (bd_t)); //gd->bd 所指向的空间清零
gd->flags |= GD_FLG_RELOC;
monitor_flash_len = _bss_start - _armboot_start; //uboot镜像文件的大小
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { //执行初始化序列函数
这里是调用了一系列的c函数指针,进行初始化。比如cpu_init初始化完成各个gpio管脚初始化,board_init完成arch_number设置和boot_params约定存放地址,还有串口初始化等。
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
board/smdk2410/flash.c配置flash
#ifndef CFG_NO_FLASH 识别出来是哪一种flash nor还是nand 如果定义了CFG_NO_FLASH这个宏,说明是nand 否则是nor
/* configure available FLASH banks */
size = flash_init (); //nor型flash的初始化
display_flash_config (size);
#endif /* CFG_NO_FLASH */
定义显示类型 分vfd和lcd两种。vfd一般不用,我们用lcd的
在这里定义了帧缓冲,也就显存的的地址和大小
-----------------------------------------------------------------------------------------------------------
#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096 //定义页大小4k
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */
#ifdef CONFIG_LCD //在内存中配置一块帧缓冲区
/* board init may have inited fb_base */
if (!gd->fb_base) {
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
#endif
/*
* reserve memory for LCD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); 按页对其方式保留显存
size = lcd_setmem (addr);分配帧缓冲区的大小
gd->fb_base = addr; //帧缓冲区的物理起始地址
}
#endif /* CONFIG_LCD */
--------------------------------------------------------------------------------------------------------------------
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //分配堆空间大小 这样才可以初始化环境变量
//初始化nand flash,这是在nand flash启动的s3c2410移植u-boot的关键,根据flash时序编写函数即可
//在include/configs/smdk2410.h中的command definition中增加CONFIG_COMMANDS和CFG_CMD_NAND命令
//nand型flash的初始化
#if defined(CONFIG_CMD_NAND)
puts ("NAND: "); 打印标志: NAND: 64MB
nand_init(); /* go init the NAND */ //board/smdk2410/smdk2410.c,获取nand的基地址和 大小信息
#endif
#if defined(CONFIG_CMD_ONENAND) 三星的一种特别的flash onenand,类似于nand
onenand_init();
#endif
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment */
env_relocate ();
//环境变量的初始化 该函数是在commen/env_commen.c中定义的,在该文件中还定义了一个默认下的环境变量
default_enviornment[] 第一次启动时,nand 默认里面是没有环境变量的,则根据板文件的宏选用默认的环境变量。
可以进行修改通过saveenv进行保存到nand里。
//env_relocate将环境变量从存储设备中读取到全局变量env_t env_ptr的data里面,由于该函数从以上堆空间
//中分配空间,所以我们的环境变量都放在了堆空间里面了
env_relocate ----> env_ptr = (env_t *)malloc (CONFIG_ENV_SIZE); gd->env_addr = (ulong)&(env_ptr->data);此处是告诉
环境变量存放在内存中的地址自此, 环境变量已经存放在内存gd->env_addr处,这样就可以获取环境变量或者
修改环境变量保存到nand中去了
#ifdef CONFIG_VFD //framebuffer初始化
/* must do this after the framebuffer is allocated */
drv_vfd_init(); //video的初始化
#endif /* CONFIG_VFD */
#ifdef CONFIG_SERIAL_MULTI //多串口
serial_initialize();
#endif
//从环境变量中获取IP地址 以太网接口MAC地址 主要是初始化 gd->bd->bi_ip_addr和gd->bd->bi_enetaddr[]
-----------------------------------------------------------------
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); 通过读取环境变量的ip地址
/* 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;
}
#ifdef CONFIG_HAS_ETH1 如果要是有两块以太网卡
i = getenv_r ("eth1addr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
#endif
}
----------------------------------------------------------------------------
devices_init (); /* get the devices list going. */ 注册设备链表,其实也就只注册了一个串口设备
#ifdef CONFIG_CMC_PU2
load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
jumptable_init ();
console_init_r ();/* fully init console as a device */
//控制台设备的初始化阶段2 到这里终于可以从控制台上看到数据打印出来了
------------------------------------------------------------------------------------------------------
注意:从串口寄存器的设置到最终在终端上打印信息,是有以下函数组成的。
在init_sequense里的三个函数和
init_baudrate, 设置 gd->bd->bi_baudrate
serial_init, 直接调用serial_setbrg函数初始化UART寄存器:8个数据位,一个开始位,一个停止位,无校验位。。。
console_init_f, 控制台前期初始化 设置gd->have_console=1
devices_init, 调用drv_system_init 注册串口设备
console_init_r 控制台后期初始化,将串口设备指向控制台标准输入设备,标准输出设备,标准错误设备
In: serial
Out: serial
Err: serial
打印这三行信息,表明串口作为标准输入设备,标准输出设备,标准错误输出设备,这样就能打印信息了
默认情况下,键盘和鼠标默认为标准输入设备,显示器默认为标准输出设备和标准错误输出设备,printf为标准格式输出
scanf为标准格式输入。标准输入,标准输出设备的重定向即将串口设备作为标准输入设备,将串口做为标准输出设备和
标准错误输出设备
static void drv_system_init (void)
static void drv_system_init (void)
{
device_t dev; //定义一个设备结构体
memset (&dev, 0, sizeof (dev));//为刚刚定义的结构体分配内存
strcpy (dev.name, "serial"); //名称
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; 设备的类型
#ifdef CONFIG_SERIAL_SOFTWARE_FIFO
dev.putc = serial_buffered_putc;
dev.puts = serial_buffered_puts;
dev.getc = serial_buffered_getc;
dev.tstc = serial_buffered_tstc;
#else
dev.putc = serial_putc;
dev.puts = serial_puts;
dev.getc = serial_getc;
dev.tstc = serial_tstc;
#endif
填充该设备结构体并注册
device_register (&dev);//注册函数 将该串口设备注册到devlist
#ifdef CFG_DEVICE_NULLDEV
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, "nulldev");
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
dev.putc = nulldev_putc;
dev.puts = nulldev_puts;
dev.getc = nulldev_input;
dev.tstc = nulldev_input;
device_register (&dev);
#endif
}
console_init_r函数
2 int console_init_r (void)
3 {
4 device_t *inputdev = NULL, *outputdev = NULL; 定义两个设备
5 int i, items = ListNumItems (devlist); // 取得设备链中的设备数 因为只注册了一个设备,即items=1
6 /* Scan devices looking for input and output devices */
7 for (i = 1;
8 (i <= items) && ((inputdev == NULL) || (outputdev == NULL));
9 i++
10 ) {
11 device_t *dev = ListGetPtrToItem (devlist, i); 从devlist中获取该串口设备
12 if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) { 执行
13 inputdev = dev; 即输入设备为该串口设备
14 }
15 if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) { 执行
16 outputdev = dev; 即输出设备也为该串口设备
17 }
18 }
// 7~18行,在设备链中按注册的顺序查找输入输出设备,在设备注册时 dev.flags表示此设备的类型。
// 比如这里drv_system_init,此设备是第一个注册的设备,且其dev.flags为
// DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM 所以上面18行过后,输入输出设备都指定为
// drv_system_init里注册的设备了
console_setfile 是最关键的地方,在这里才是真正的给我们的标准输入设备,标准输出设备,标准错误输出设备填充结构
19 /* Initializes output console first */
20 if (outputdev != NULL) {
通过console_setfile()函数可以看出,控制台有一个包含 3 个 device_t 元素的数组stdio_devices,分别对应
stdin,stdout,stderr。
通过 stdio_devices[file] = dev 就可以将dev设成设置控制台的某个设备。这样就实现了控制台任意选择设备的功能。
这和 linux 的设计思想有点类似。
21 console_setfile (stdout, outputdev); 即stdio_devices[stdout]=outputdev
22 console_setfile (stderr, outputdev); 即stdio_devices[stderr]=outputdev
23 }
24 /* Initializes input console */
25 if (inputdev != NULL) {
26 console_setfile (stdin, inputdev); 即stdio_devices[stdin]=inputdev
27 } 将控制台的标准输入设备,标准输出设备,标注错误输出设备均设为串口设备
21~27行, console_setfile做如下几件事:
1. 如果初始化该设备时注册了device_t.start,即启动设备的函数,则运行该函数,开启该设备
2. 将设备的指针存入stdio_devices[file],这应该是标准输入标准输出、标准出错。
#define stdin 0
#define stdout 1
#define stderr 2
22行将标准出错定为输出设备,这样有错误信息就会通过输出设备打印出来了
28 gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
29 /* Print information */
30 puts ("In: ");
31 if (stdio_devices[stdin] == NULL) {
32 puts ("No input devices available!\n");
33 } else {
34 printf ("%s\n", stdio_devices[stdin]->name);
35 }
36 puts ("Out: ");
37 if (stdio_devices[stdout] == NULL) {
38 puts ("No output devices available!\n");
39 } else {
40 printf ("%s\n", stdio_devices[stdout]->name);
41 }
42 puts ("Err: ");
43 if (stdio_devices[stderr] == NULL) {
44 puts ("No error devices available!\n");
45 } else {
46 printf ("%s\n", stdio_devices[stderr]->name);
47 }
30~47行,将信息打印出来,这里打印出出的信息就为
In: serial
Out: serial
Err: serial
48 /* Setting environment variables */
49 for (i = 0; i < 3; i++) {
50 setenv (stdio_names[i], stdio_devices[i]->name);// 即stdin=serial
51 } stdout = serial
49~51行 将信息写到环境变量中去
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
这样环境变量里 stdin stdout stderr 都为serial
52 return (0);
53 }
---------------------------------------------------------------------------------------------------------------------------------------
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r (); //混杂设备的初始化 很重要
#endif
/* enable exceptions */
enable_interrupts (); //使能中断 即打开cpsr_c的第7位 即外中断,该第7位值为0,表示使能外中断
/* Perform network card initialisation if necessary */
配置几种类型的网卡
#ifdef CONFIG_DRIVER_TI_EMAC
extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
if (getenv ("ethaddr")) {
davinci_eth_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif
#ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
debug ("Reset Ethernet PHY\n");
reset_phy();
#endif
#endif
/*****************************************非常重要**************************************/
/* main_loop() can return to retry autoboot, if so just run it again. */
// for(;;)与while(1) 是一样的,for(;;)编译成汇编后是无条件转移,while(1)是要0和1进行一下比较的,
所以从这个方向上看for(;;)是要比while(1)快的因为少了一个比较指令,但现在的编译器都是有一定的
优化能力的,像while(1)这种会优化成和for(;;)一样的汇编代码
for (;;) {
main_loop ();
//进入主循环 主循环函数处理执行用户命令 main_loop 是在common/main.c中定义和实现的
}
/* NOTREACHED - no way out of command loop except booting */
}
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
#ifdef CONFIG_MODEM_SUPPORT
static inline void mdm_readline(char *buf, int bufsiz);
/* called from main loop (common/main.c) */
extern void dbg(const char *fmt, ...);
int mdm_init (void)
{
char env_str[16];
char *init_str;
int i;
extern char console_buffer[];
extern void enable_putc(void);
extern int hwflow_onoff(int);
enable_putc(); /* enable serial_putc() */
#ifdef CONFIG_HWFLOW
init_str = getenv("mdm_flow_control");
if (init_str && (strcmp(init_str, "rts/cts") == 0))
hwflow_onoff (1);
else
hwflow_onoff(-1);
#endif
for (i = 1;;i++) {
sprintf(env_str, "mdm_init%d", i);
if ((init_str = getenv(env_str)) != NULL) {
serial_puts(init_str);
serial_puts("\n");
for(;;) {
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini%d: [%s]", i, console_buffer);
if ((strcmp(console_buffer, "OK") == 0) ||
(strcmp(console_buffer, "ERROR") == 0)) {
dbg("ini%d: cmd done", i);
break;
} else /* in case we are originating call ... */
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini%d: connect", i);
return 0;
}
}
} else
break; /* no init string - stop modem init */
udelay(100000);
}
udelay(100000);
/* final stage - wait for connect */
for(;i > 1;) { /* if 'i' > 1 - wait for connection
message from modem */
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini_f: [%s]", console_buffer);
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini_f: connected");
return 0;
}
}
return 0;
}
/* 'inline' - We have to do it fast */
static inline void mdm_readline(char *buf, int bufsiz)
{
char c;
char *p;
int n;
n = 0;
p = buf;
for(;;) {
c = serial_getc();
/* dbg("(%c)", c); */
switch(c) {
case '\r':
break;
case '\n':
*p = '\0';
return;
default:
if(n++ > bufsiz) {
*p = '\0';
return; /* sanity check */
}
*p = c;
p++;
break;
}
}
}
#endif /* CONFIG_MODEM_SUPPORT */
以下是main_loop函数的分析 该文件在common/main.c中
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER // CFG_HUSH_PARSER没有定义,所以执行
static char lastcommand[CFG_CBSIZE] = { 0, }; CFG_CBSIZE为256 用于记录console buffer size
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 执行 定义指针s和int型bootdelay。用于uboot判断
无任何按键按下,就执行CONFIG_BOOTCOMMAND宏所对应的命令,这个命令通常用于加载启动操作系统;
char *s;
int bootdelay;
#endif
#ifdef CONFIG_PREBOOT 没定义,不执行
char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT 没定义 不执行
unsigned long bootcount = 0;
unsigned long bootlimit = 0;
char *bcs;
char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO) 没定义 但是我们可以定义,让显示开机log
ulong bmp = 0; /* default bitmap */
extern int trab_vfd (ulong bitmap);
#ifdef CONFIG_MODEM_SUPPORT 没定义
if (do_mdm_init)
bmp = 1; /* alternate bitmap */
#endif
trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */
#ifdef CONFIG_BOOTCOUNT_LIMIT 没定义 不执行
bootcount = bootcount_load();
bootcount++;
bootcount_store (bootcount);
sprintf (bcs_set, "%lu", bootcount);
setenv ("bootcount", bcs_set);
bcs = getenv ("bootlimit");
bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#ifdef CONFIG_MODEM_SUPPORT 暂时不支持通话 没定义 不执行
debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);
if (do_mdm_init) {
char *str = strdup(getenv("mdm_cmd"));
setenv ("preboot", str); /* set or delete definition */
if (str != NULL)
free (str);
mdm_init(); /* wait for modem connection */
}
#endif /* CONFIG_MODEM_SUPPORT */
#ifdef CONFIG_VERSION_VARIABLE 没定义 不执行
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */ 设置环境变量ver=“”
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CFG_HUSH_PARSER 没定义 不执行
u_boot_hush_start ();
#endif
#ifdef CONFIG_AUTO_COMPLETE 该宏非常好用,可以让我们自动补齐命令
install_auto_complete();
#endif
#ifdef CONFIG_PREBOOT 没定义 不执行
if ((p = getenv ("preboot")) != NULL) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (p, 0);
# else
parse_string_outer(p, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
#endif /* CONFIG_PREBOOT */
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay"); //获取环境变量bootdelay的值 即将环境变量bootdelay等号后面的值的地址保存在s ,即s保存字符串的地址,比如是5,即5此时是字符串
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; //如果环境变量中没有bootdelay参数,则就用默认的CONFIG_BOOTDELAY来当作倒数计时
bootdelay保存该环境变量的值
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
# ifdef CONFIG_BOOT_RETRY_TIME 不执行
init_cmd_timeout ();
# endif /* CONFIG_BOOT_RETRY_TIME */
#ifdef CONFIG_POST
if (gd->flags & GD_FLG_POSTFAIL) {
s = getenv("failbootcmd");
}
else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT 不执行
if (bootlimit && (bootcount > bootlimit)) {
printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv ("altbootcmd");
}
else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
*******************************************************************************************************************************************
以下是两种情况
第一种情况是 在bootdelay内不按空格键:s=getenv ("bootcmd");run_command (s, 0);直接启动内核了
第二种情况就是 在bootdelay内按下空格键: s = getenv("menucmd");run_command (s, 0);进入命令循环
len = readline (CFG_PROMPT);run_command (lastcommand, flag);之后在run_command里
if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根据命令的名字
找到该命令表结构体,调用它的cmd参数 最终还是回到do_xxx
比如 run_command("bootm")---->根据“bootm”名字会在命令表里去找到相应的命令表,最终用该命令表的
cmd参数即do_bootm去处理宏U_BOOT_CMD定义了一个段属性为.u_boot_cmd的命令表结构体 struct cmd_tbl_t
s = getenv ("bootcmd"); //启动命令 非常重要 bootcmd = nand read 目的地址 源地址; bootm 目的地址
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //如果倒数计时之内没有按空格键 则直接
run_command (s, 0) 直接启动内核了
# ifdef CONFIG_AUTOBOOT_KEYED 不执行
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (s, 0); //直接启动内核了
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
# ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd"); //否则如果在倒数计时之内按下了空格键,就会跑到这里来
if (s) {
# ifndef CFG_HUSH_PARSER
run_command (s, 0); //就会进入到菜单里面 当然你需要自己去实现这个菜单
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
#ifdef CONFIG_AMIGAONEG3SE
{
extern void video_banner(void);
video_banner();
}
#endif
/*
* Main Loop for Monitor Command Processing //进入循环 处理用户输入的命令
*/
#ifdef CFG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else //执行else分支 在这里进入循环来处理各种用户输入的命令
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
if (rc >= 0) {
/* Saw enough of a valid command to
* restart the timeout.
*/
reset_cmd_timeout();
}
#endif
len = readline (CFG_PROMPT); //读取串口里用户输入的命令 一回车这些输入的字符串就被考入到console_buffer里 len表示获取的字符串长度
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer); //将用户输入的命令字符串拷贝到lastcommand里
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
else if (len == -2) {
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
/* Reinit board to run initialization code again */
do_reset (NULL, 0, 0, NULL);
# else
return; /* retry autoboot */
# endif
}
#endif
if (len == -1)
puts ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag); //解析和处理用户输入的命令
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
#endif /*CFG_HUSH_PARSER*/
}
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
main_loop又臭又长,去掉宏注释掉的部分就只剩下一点点了。如下:
void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_AUTO_COMPLETE
install_auto_complete(); //安装自动补全的函数,分析如下 。
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d/n/n", bootdelay);
s = getenv ("bootcmd"); //获取引导命令。分析见下面。
debug ("### main_loop: bootcmd=/"%s/"/n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {//如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。abortboot函数的分析见下面。
/* 重点1 */
run_command (s, 0); //运行引导内核的命令。这个命令是在配置头文件中定义的。run_command的分析在下面。
}
#endif /* CONFIG_BOOTDELAY */
for (;;) { 否则就进入uboot命令行执行各个uboot命令了
len = readline (CONFIG_SYS_PROMPT); //CONFIG_SYS_PROMPT的意思是回显字符,一般是“>”。这是由配置头文件定义的 readline读入用户输入的字符串,存放在console_buffer
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 == -1)
puts ("<INTERRUPT>/n");
else
rc = run_command (lastcommand, flag); //执行命令 ,重点2。
if (rc <= 0) {//执行失败,则清空记录
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
}
2。自动补全
common/common.c
int var_complete(int argc, char *argv[], char last_char, int maxv, char *cmdv[])
{
static char tmp_buf[512];
int space;
space = last_char == '/0' || last_char == ' ' || last_char == '/t';
if (space && argc == 1)
return env_complete("", maxv, cmdv, sizeof(tmp_buf), tmp_buf);
if (!space && argc == 2)
return env_complete(argv[1], maxv, cmdv, sizeof(tmp_buf), tmp_buf);
return 0;
}
static void install_auto_complete_handler(const char *cmd,
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]))
{
cmd_tbl_t *cmdtp;
cmdtp = find_cmd(cmd);
if (cmdtp == NULL)
return;
cmdtp->complete = complete; //命令结构体的complete指针指向传入的函数。
}
void install_auto_complete(void)
{
#if defined(CONFIG_CMD_EDITENV)
install_auto_complete_handler("editenv", var_complete);
#endif
install_auto_complete_handler("printenv", var_complete);
install_auto_complete_handler("setenv", var_complete);
#if defined(CONFIG_CMD_RUN)
install_auto_complete_handler("run", var_complete);
#endif
}
可以看到将editenv、printenv、setenv和run的自动补全函数安装为 var_complete。
var_complete的功能是根据给出的前缀字符串,找出所有前缀相同的命令。
每个命令在内存中用一个cmd_tbl_t 表示。
include/command.h
struct cmd_tbl_s {
char *name; /* 命令名,输入的就是它 */
int maxargs; /* 最大参数个数 */
int repeatable; /* 允许自动重发,也就是在按下空格键之后执行最后一条命令。 */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 实现命令的参数 */
char *usage; /* 短的提示信息 */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* 详细的帮助信息。 */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
extern cmd_tbl_t __u_boot_cmd_start;
extern cmd_tbl_t __u_boot_cmd_end;
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) / U_BOOT_CMD宏定义了一个cmd_tbl_t结构的uboot命令,用find_cmd(argv[0])) 参数是命令的名字,根据名字找到该命令结构cmd_tbl_t
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
#define U_BOOT_CMD_MKENT(name,maxargs,rep,cmd,usage,help) /
{#name, maxargs, rep, cmd, usage, help}
uboot中的命令使用U_BOOT_CMD这个宏声明来注册进系统,链接脚本会把所有的cmd_tbl_t结构体放在相邻的地方。
链接脚本中的一些内容如下:
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
可见,__u_boot_cmd_start 和__u_boot_cmd_end 分别对应命令结构体在内存中开始和结束的地址。
3。abortboot函数的分析
abortboot是uboot在引导期间的延时函数。期间可以按键进入uboot的命令行。
common/main.c
static __inline__ int abortboot(int bootdelay) 该函数 即在延时时间内,若扫描有键按下即返回1,若无键按下 则返回0
{
int abort = 0;
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#if defined CONFIG_ZERO_BOOTDELAY_CHECK //如果定义了这个宏,即使定义延时为0,也会检查一次是否有按键按下。只要在这里执行之前按键,还是能进入uboot的命令行。
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press */ 测试是否有按键按下
(void) getc(); /* consume input */接收按键值
puts ("/b/b/b 0");
abort = 1; /* don't auto boot */修改标记,停止自动引导
}
}
#endif
while ((bootdelay > 0) && (!abort)) { //如果延时大于零并且停止标记没有赋值则进入延时循环,直到延时完或者接收到了按 键
int i;
--bootdelay;
/* delay 100 * 10ms */ 每秒中测试按键100次,之后延时10ms。
for (i=0; !abort && i<100; ++i) {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */*/修改标记,停止自动引导
bootdelay = 0; /* no more delay */延时归零
(void) getc(); /* consume input */获取按键
break;
}
udelay(10000);//延时10000us,也就是10ms
}
printf("/b/b/b%2d ", bootdelay);//打印当前剩余时间
}
putc('/n');
return abort;//返回结果:1-停止引导,进入命令行; 0-引导内核。
}
可以看到uboot延时的单位是秒,如果想提高延时的精度,比如想进行10ms级的延时,将udelay(10000)改为udelay(100)就可以了 。
run_command 重点
//
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CONFIG_SYS_CBSIZE];
char *str = cmdbuf;
char *argv[CONFIG_SYS_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) >= CONFIG_SYS_CBSIZE) { //命令太长
puts ("## Command too long!/n");
return -1;
}
strcpy (cmdbuf, cmd); //将命令拷贝到临时命令缓冲cmdbuf
/* Process separators and check for invalid
* repeatable commands
*/
while (*str) { //str指向cmdbuf
/*
* 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;
}
/*
* Limit the token to data between separators
*/
token = str; //token指向命令的开头
if (*sep) { //如果是分隔符的话,将分隔符替换为空字符
str = sep + 1; /* start of command for next pass */str指向下一句的开头
*sep = '/0';
}
else
str = sep; /* no more commands for next pass */如果没有其它命令了,str指向命令尾部
/* find macros in this token and replace them */
process_macros (token, finaltoken);
//将token指向的命令中的宏替换掉,如把$(kernelsize)替换成内核的大小
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
//将每一个参数用‘/0’隔开,argv中的每一个指针指向一个参数的起始地址。 返回值为参数的个数
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) { //检查参数个数是否过多
cmd_usage(cmdtp);
rc = -1;
continue;
}
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {//调用命令执行函数。这是最重要的一步。
rc = -1;
}
repeatable &= cmdtp->repeatable;
//设置命令自动重复执行的标志。也就是只按下enter键是否可以执行最近执行的命令 .
/* Did the user stop this? */
if (had_ctrlc ())
//检查是否有ctrl+c按键按下,如果有,结束当前命令。本函数并没有从中断接收字符,接收ctrl+c的是一些执行命令的函数。
return -1; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
---------------------------------------------------------------------------------------------------
其实 main_loop函数就是处理两个run_command函数,一个是在开机延迟时间内若没有按下任意键,则
直接启动内核(bootcmd);一个是若按下了,则不会启动内核,处理uboot命令
uboot最核心的东西就是命令,关于uboot命令的章节笔记本上都有,此处略。
现在来看uboot的最后一部分 启动内核
启动内核 s=getenv ("bootcmd");run_command (s, 0);
环境变量bootcmd = nand read 目的地址 源地址 大小; bootm 目的地址
只要是启动内核,就一定要用到bootcmd,执行这两条指令
nand read指令的实现是在cmd_nand.c文件中的do_nand函数中实现的,do_nand函数中有很多关于nand命令
比如 nand write,nand erase等等
***************************************************************************************************************
我们来看下关于nand相关的指令实现的函数 do_nand
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
int i, dev, ret = 0;
ulong addr, off;
size_t size;
char *cmd, *s;
nand_info_t *nand;
#ifdef CFG_NAND_QUIET
int quiet = CFG_NAND_QUIET;
#else
int quiet = 0;
#endif
const char *quiet_str = getenv("quiet");
/* at least two arguments please */
if (argc < 2) //r如果参数的个数小于2个 退出,也即参数的个数至少是两个 如nand read
goto usage;
if (quiet_str)
quiet = simple_strtoul(quiet_str, NULL, 0) != 0;
cmd = argv[1]; //获取nand 后面的那一个命令
if (strcmp(cmd, "info") == 0) { //命令 nand info
putc('\n');
for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {
if (nand_info[i].name)
printf("Device %d: %s, sector size %u KiB\n",
i, nand_info[i].name,
nand_info[i].erasesize >> 10);
}
return 0;
}
if (strcmp(cmd, "device") == 0) { // 命令 nand device
if (argc < 3) {
if ((nand_curr_device < 0) ||
(nand_curr_device >= CFG_MAX_NAND_DEVICE))
puts("\nno devices available\n");
else
printf("\nDevice %d: %s\n", nand_curr_device,
nand_info[nand_curr_device].name);
return 0;
}
dev = (int)simple_strtoul(argv[2], NULL, 10);
if (dev < 0 || dev >= CFG_MAX_NAND_DEVICE || !nand_info[dev].name) {
puts("No such device\n");
return 1;
}
printf("Device %d: %s", dev, nand_info[dev].name);
puts("... is now current device\n");
nand_curr_device = dev;
#ifdef CFG_NAND_SELECT_DEVICE
/*
* Select the chip in the board/cpu specific driver
*/
board_nand_select_device(nand_info[dev].priv, dev);
#endif
return 0;
}
if (strcmp(cmd, "bad") != 0 && strcmp(cmd, "erase") != 0 &&
strncmp(cmd, "dump", 4) != 0 &&
strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 &&
strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 &&
strcmp(cmd, "biterr") != 0 &&
strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 )
goto usage;
/* the following commands operate on the current device */
if (nand_curr_device < 0 || nand_curr_device >= CFG_MAX_NAND_DEVICE ||
!nand_info[nand_curr_device].name) {
puts("\nno devices available\n");
return 1;
}
nand = &nand_info[nand_curr_device];
if (strcmp(cmd, "bad") == 0) {
printf("\nDevice %d bad blocks:\n", nand_curr_device);
for (off = 0; off < nand->size; off += nand->erasesize)
if (nand_block_isbad(nand, off))
printf(" %08lx\n", off);
return 0;
}
/*
* Syntax is:
* 0 1 2 3 4
* nand erase [clean] [off size]
*/
if (strcmp(cmd, "erase") == 0 || strcmp(cmd, "scrub") == 0) {
nand_erase_options_t opts;
/* "clean" at index 2 means request to write cleanmarker */
int clean = argc > 2 && !strcmp("clean", argv[2]);
int o = clean ? 3 : 2;
int scrub = !strcmp(cmd, "scrub");
printf("\nNAND %s: ", scrub ? "scrub" : "erase");
/* skip first two or three arguments, look for offset and size */
if (arg_off_size(argc - o, argv + o, nand, &off, &size) != 0)
return 1;
memset(&opts, 0, sizeof(opts));
opts.offset = off;
opts.length = size;
opts.jffs2 = clean;
opts.quiet = quiet;
if (scrub) {
puts("Warning: "
"scrub option will erase all factory set "
"bad blocks!\n"
" "
"There is no reliable way to recover them.\n"
" "
"Use this command only for testing purposes "
"if you\n"
" "
"are sure of what you are doing!\n"
"\nReally scrub this NAND flash? <y/N>\n");
if (getc() == 'y' && getc() == '\r') {
opts.scrub = 1;
} else {
puts("scrub aborted\n");
return -1;
}
}
ret = nand_erase_opts(nand, &opts); //真正解析该命令的函数
printf("%s\n", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
if (strncmp(cmd, "dump", 4) == 0) {
if (argc < 3)
goto usage;
s = strchr(cmd, '.');
off = (int)simple_strtoul(argv[2], NULL, 16);
if (s != NULL && strcmp(s, ".oob") == 0)
ret = nand_dump(nand, off, 1);
else
ret = nand_dump(nand, off, 0);
return ret == 0 ? 1 : 0;
}
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
//命令是 nand read 和nand write 命令的解析
int read;
if (argc < 4)
goto usage;
addr = (ulong)simple_strtoul(argv[2], NULL, 16);
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
printf("\nNAND %s: ", read ? "read" : "write");
if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
return 1;
s = strchr(cmd, '.');
if (!s || !strcmp(s, ".jffs2") ||
!strcmp(s, ".e") || !strcmp(s, ".i")) {
if (read)
ret = nand_read_skip_bad(nand, off, &size,
(u_char *)addr);
else
ret = nand_write_skip_bad(nand, off, &size,
(u_char *)addr);
} else if (s != NULL && !strcmp(s, ".oob")) {
/* out-of-band data */
mtd_oob_ops_t ops = {
.oobbuf = (u8 *)addr,
.ooblen = size,
.mode = MTD_OOB_RAW
};
if (read)
ret = nand->read_oob(nand, off, &ops);
else
ret = nand->write_oob(nand, off, &ops);
} else {
printf("Unknown nand command suffix '%s'.\n", s);
return 1;
}
printf(" %d bytes %s: %s\n", size,
read ? "read" : "written", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
if (strcmp(cmd, "markbad") == 0) {
addr = (ulong)simple_strtoul(argv[2], NULL, 16);
int ret = nand->block_markbad(nand, addr);
if (ret == 0) {
printf("block 0x%08lx successfully marked as bad\n",
(ulong) addr);
return 0;
} else {
printf("block 0x%08lx NOT marked as bad! ERROR %d\n",
(ulong) addr, ret);
}
return 1;
}
if (strcmp(cmd, "biterr") == 0) {
/* todo */
return 1;
}
if (strcmp(cmd, "lock") == 0) {
int tight = 0;
int status = 0;
if (argc == 3) {
if (!strcmp("tight", argv[2]))
tight = 1;
if (!strcmp("status", argv[2]))
status = 1;
}
/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
if (status) {
ulong block_start = 0;
ulong off;
int last_status = -1;
struct nand_chip *nand_chip = nand->priv;
/* check the WP bit */
nand_chip->cmdfunc (nand, NAND_CMD_STATUS, -1, -1);
printf("device is %swrite protected\n",
(nand_chip->read_byte(nand) & 0x80 ?
"NOT " : ""));
for (off = 0; off < nand->size; off += nand->writesize) {
int s = nand_get_lock_status(nand, off);
/* print message only if status has changed
* or at end of chip
*/
if (off == nand->size - nand->writesize
|| (s != last_status && off != 0)) {
printf("%08lx - %08lx: %8d pages %s%s%s\n",
block_start,
off-1,
(off-block_start)/nand->writesize,
((last_status & NAND_LOCK_STATUS_TIGHT) ? "TIGHT " : ""),
((last_status & NAND_LOCK_STATUS_LOCK) ? "LOCK " : ""),
((last_status & NAND_LOCK_STATUS_UNLOCK) ? "UNLOCK " : ""));
}
last_status = s;
}
} else {
if (!nand_lock(nand, tight)) {
puts("NAND flash successfully locked\n");
} else {
puts("Error locking NAND flash\n");
return 1;
}
}
#endif
return 0;
}
if (strcmp(cmd, "unlock") == 0) {
if (arg_off_size(argc - 2, argv + 2, nand, &off, &size) < 0)
return 1;
/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
if (!nand_unlock(nand, off, size)) {
puts("NAND flash successfully unlocked\n");
} else {
puts("Error unlocking NAND flash, "
"write and erase will probably fail\n");
return 1;
}
#endif
return 0;
}
usage:
printf("Usage:\n%s\n", cmdtp->usage);
return 1;
}
**********************************************************************************************************************************
bootm命令
bootm命令的实现
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
…… ……
/* 检查头部 */
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
…… ……
/*解压缩*/
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); //将真正的内核移动到加载地址处hdr->ih_load
#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;
}
}
…… …… ……
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX: //惊奇!!!!!!!!!!!!!!!!!!!在这里调用了do_bootm_linux函数
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;
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
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;
}
bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。
bootm命令主要做三件事情:
1,解析uImage头部信息,获取image_header结构体
2,将真正的内核移动到加载地址处,如果相等就不用移动
3,根据操作系统,选择启动内核的函数,如do_bootm_linux函数
3.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)
{
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
… … … …
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
… … … …
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); /*启动内核,传递启动参数*/
}
do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。
U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。
以下是bootm.c里的do_bootm_linux的源码
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number; //机器ID
void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs"); //获取启动参数的命令行
#endif
theKernel = (void (*)(int, int, uint))images->ep; // the_kernel即镜像文件的入口地址
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
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); //设置内存TAG,会用到bd->bi_dram[].start bd->bi_dram[].size
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif
/* 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 ();
theKernel (0, machid, bd->bi_boot_params); //启动内核,一去不复返
/* does not return */
return 1;
}
第二阶段小结: bootcmd
主要是3个关键函数 start_armboot ----> main_loop ------> do_bootm_linux
当我们在倒数计时时按下空格键进入uboot命令界面,我们若输入boot命令回车,便会调用bootcmd环境变量的两条指令,同样启动内核
我们按下空格时出现的菜单 是需要自己去实现的,在common里创建一个新文件cmd_menu.c里去实现
mkimage使用详解
###############################################################################################################################
mkimage使用详解(转载) uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
root@Glym:/tftpboot# ./mkimage
Usage: ./mkimage -l image
-l ==> list image header information
./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
参数说明:
-A 指定CPU的体系结构:
取值 表示的体系结构
alpha Alpha
arm A RM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64 Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64 Bit
m68k MC68000
-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem
-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式
-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映象名
-d 指定制作映象的源文件
U-BOOT下使用bootm引导内核方法
一、在开始之前先说明一下bootm相关的东西。
1、 首先说明一下,S3C2410架构下的bootm只对sdram中的内核镜像文件进行操作(好像AT91架构提供了一段从flash复制内核镜像的代码, 不过针对s3c2410架构就没有这段代码,虽然可以在u-boot下添加这段代码,不过好像这个用处不大),所以请确保你的内核镜像下载到sdram 中,或者在bootcmd下把flash中的内核镜像复制到sdram中。
2、-a参数后是内核的运行地址,-e参数后是入口地址。
3、 在这里,有个非常关键的问题,对于uboot启动内核,我们经常用bootm命令来启动内核,bootm->do_bootm->do_bootm_linux->set_xxx_tag,然后the_kernel
1)如果我们没用mkimage对内核进行处理的话,那直接把内核下载到0x30008000再运行就行,内核会自解压运行(不过内核运行需要一个tag来传递参数,而这个tag建议是由bootloader提供的,在u-boot下默认是由bootm命令建立的)。
!!!!! 要紧记这两种情况
2)如果使用mkimage生成内核镜像文件的话,会在内核的前头加上了64byte的信息,供建立tag之用。bootm命令会首先判断bootm xxxx 这个指定的地址xxxx是否与-a指定的加载地址相同。
(1)如果不同的话会从这个地址开始提取出这个64byte的头部,对其进行分析,然后把去掉头部的内核复制到-a指定的load地址中去运行之
(2)如果相同的话那就让其原封不同的放在那,但-e指定的入口地址会推后64byte,以跳过这64byte的头部。
如果启动地址与内核镜像的加载地址相同,那么镜像就不用移动,但是镜像的入口地址必须后移64个字节
如果启动地址与内核镜像的加载地址不同,那么镜像需要移动,此时,要将去掉64字节的头部的镜像移动到加载地址处
二、好,接着介绍使用mkimage生成镜像文件并下载运行的方法。
方法一、
1、首先,用u-boot/tools/mkimage这个工具为你的内核加上u-boot引导所需要的文件头,具体做法如下:
[root@localhost tftpboot]#mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage zImage.img // -d zImage 表示原始映像文件 zImage.img 表示生成的最终镜像文件
Image Name: linux-2.6.14
Created: Fri Jan 12 17:14:50 2007
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1262504 Bytes = 1232.91 kB = 1.20 MB
Load Address: 0x30008000
Entry Point: 0x30008000
这里解释一下参数的意义:
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type'
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place)
2 、下载内核
U-Boot 1.1.3 (Jan 12 2007 - 16:16:36) 经计算uboot镜像的大小为127K左右
U-Boot code: 33F80000 -> 33F9BAC0 BSS: -> 33F9FBAC 在这里调用init_senqunce函数序列中的display_banner,分别打印的是 printf ("\n\n%s\n\n", version_string);
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
RAM Configuration: 在这里调用init_senqunce函数序列中disply_dram_config函数
Bank #0: 30000000 64 MB 打印是内存的起始物理地址和大小
Nor Flash: 512 kB nor flash的初始化
Nand Flash: 64 MB 调用nand flash的初始化
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
sbc2410=>tftp 0x31000000 zImage.img
TFTP from server 192.168.1.115; our IP address is 192.168.1.128
Filename 'zImage.img'.
Load address: 0x31000000
Loading: #################################################################
#################################################################
#################################################################
####################################################
done
Bytes transferred = 1263324 (1346dc hex)
3.运行
sbc2410=>bootm 0x31000000
## Booting image at 31000000 ...
Image Name: linun-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1263260 Bytes = 1.2 MB
Load Address: 30008000
Entry Point: 30008000
Verifying Checksum ... OK
OK
Starting kernel ...
Uncompressing Linux.............................................................Linux version 2.6.14 (root@luofuchong) (gcc version 3.4.1) #21 Fri Oct 20 17:206CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T)
Machine: SMDK2410
Memory policy: ECC disabled, Data cache writeback
CPU S3C2410A (id 0x32410002)
S3C2410: core 202.800 MHz, memory 101.400 MHz, peripheral 50.700 MHz
S3C2410 Clocks, (c) 2004 Simtec Electronics
CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on
USB Control, (c) 2006 sbc2410
CPU0: D VIVT write-back cache
CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
Built 1 zonelists
Kernel command line: console="ttySAC0" root="/dev/nfs" nfsroot="192".168.1.115:/frien"irq: clearing subpending status 00000002
PID hash table entries: 512 (order: 9, 8192 bytes)
timer tcon="00500000", tcnt a509, tcfg 00000200,00000000, usec 00001e4c
Console: colour dummy device 80x30
Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
Memory: 64MB = 64MB total
Memory: 62208KB available (1924K code, 529K data, 108K init)
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
softlockup thread 0 started up.
NET: Registered protocol family 16
S3C2410: Initialising architecture
SCSI subsystem initialized
usbcore: registered new driver usbfs
usbcore: registered new driver hub
S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics
DMA channel 0 at c4800000, irq 33
DMA channel 1 at c4800040, irq 34
DMA channel 2 at c4800080, irq 35
DMA channel 3 at c48000c0, irq 36
NetWinder Floating Point Emulator V0.97 (double precision)
devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au)
devfs: devfs_debug: 0x0
devfs: boot_options: 0x1
yaffs Oct 18 2006 12:39:51 Installing.
Console: switching to colour frame buffer device 30x40
fb0: s3c2410fb frame buffer device
fb1: Virtual frame buffer device, using 1024K of video memory
led driver initialized
s3c2410 buttons successfully loaded
s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410
s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410
s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410
io scheduler noop registered
io scheduler anticipatory registered
io scheduler deadline registered
io scheduler cfq registered
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
usbcore: registered new driver ub
Cirrus Logic CS8900A driver for Linux (Modified for SMDK2410)
eth0: CS8900A rev E at 0xe0000300 irq="53", no eeprom , addr: 08: 0:3E:26:0A:5B
S3C24XX NAND Driver, (c) 2004 Simtec Electronics
s3c2410-nand: mapped registers at c4980000
s3c2410-nand: timing: Tacls 10ns, Twrph0 30ns, Twrph1 10ns
NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bi)Scanning device for bad blocks
Bad eraseblock 1884 at 0x01d70000
Creating 4 MTD partitions on "NAND 64MiB 3,3V 8-bit":
0x00000000-0x00020000 : "vivi"
0x00020000-0x00030000 : "param"
0x00030000-0x00200000 : "kernel"
0x00200000-0x04000000 : "root"
usbmon: debugfs is not available
s3c2410-ohci s3c2410-ohci: S3C24XX OHCI
s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1
s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
Initializing USB Mass Storage driver...
usbcore: registered new driver usb-storage
USB Mass Storage support registered.
usbcore: registered new driver usbmouse
drivers/usb/input/usbmouse.c: v1.6:USB HID Boot Protocol mouse driver
mice: PS/2 mouse device common for all mice
s3c2410 TouchScreen successfully loaded
UDA1341 audio driver initialized
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 4096 (order: 2, 16384 bytes)
TCP bind hash table entries: 4096 (order: 2, 16384 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP reno registered
TCP bic registered
NET: Registered protocol family 1
IP-Config: Complete:
device=eth0, addr="192".168.1.128, mask="255".255.255.0, gw="192".168.1.1,
host="luofuchong", domain=, nis-domain=(none),
bootserver="192".168.1.1, rootserver="192".168.1.115, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.115
Looking up port of RPC 100005/1 on 192.168.1.115
VFS: Mounted root (nfs filesystem).
Mounted devfs on /dev
Freeing init memory: 108K
init started: BusyBox v1.1.3 (2006.09.20-14:52+0000) multi-call binary
Starting pid 696, console /dev/tts/0: '/etc/init.d/rcS'
Please press Enter to activate this console.
方法二、
1、首先,用u-boot/tools/mkimage这个工具为你的内核加上u-boot引导所需要的文件头,具体做法如下:
[root@localhost tftpboot]#mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage zImage.img
Image Name: linux-2.6.14
Created: Fri Jan 12 17:14:50 2007
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1262504 Bytes = 1232.91 kB = 1.20 MB
Load Address: 0x30008000
Entry Point: 0x30008040
2 、下载内核
U-Boot 1.1.3 (Jan 12 2007 - 16:16:36)
U-Boot code: 33F80000 -> 33F9BAC0 BSS: -> 33F9FBAC
RAM Configuration:
Bank #0: 30000000 64 MB
Nor Flash: 512 kB
Nand Flash: 64 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
sbc2410=>tftp 0x30008000 zImage.img
TFTP from server 192.168.1.115; our IP address is 192.168.1.128
Filename 'zImage.img'.
Load address: 0x30008000
Loading: #################################################################
#################################################################
#################################################################
####################################################
done
Bytes transferred = 1263324 (1346dc hex)
3.运行
sbc2410=>bootm 0x30008000
## Booting image at 30008000 ...
Image Name: linux-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1261056 Bytes = 1.2 MB
Load Address: 30008000
Entry Point: 30008040
Verifying Checksum ... OK
XIP Kernel Image ... OK
============================================================对于uboot两阶段的小结,很重要!!!==================================
第一阶段:
在脚本uboot.lds中 ENTRY(_start) _start的地址一般是TEXT_BASE
设置异常向量表
进入svc管理模式 arm状态
关看门狗 关中断 时钟初始化
cpu初始化(关mmu 关数据和指令缓存 cpu速率)和内存初始化 在函数cpu_init_crit中完成
重定位
设置栈
清bss断
跳入到start_armboot函数
第二阶段 就进入到lib_arm/board.c中的start_kernel函数
分配gd指针指向的空间和gd->bd指针指向的空间
执行init_sequence函数序列,其实主要目的是初始化并填充gd和gd->bd结构体
分配堆空间mem_malloc_init ,这样才可以初始化环境变量,因为环境变量是要从nand拷贝到内存中的堆空间里
nand初始化 nand_init,因为现在普及采用nand,若是用nor的话,在之前已经初始化了 flash_init函数
环境变量初始化 env_reloacate 有四个很重要的环境变量参数:bootdelay(启动延时),bootcmd(启动命令),menucmd(菜单),bootargs(启动参数,也即最原始的命令行参数)
串口设置的5个函数,这样就能看到串口打印数据
混杂设备 misc_init_r函数
进入循环执行 main_loop函数,处理启动命令或者用户输入的命令 该函数是在common/main.c中
第一种情况是 在bootdelay内不按空格键:s=getenv ("bootcmd");run_command (s, 0);直接启动内核了
第二种轻狂就是 在bootdelay内按下空格键进入menu菜单里: s = getenv("menucmd");run_command (s, 0);然后进入命令循环获取用户从串口里打印的字符 len = readline (CFG_PROMPT);run_command (lastcommand, flag); 之后在run_command里if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根据 命令的名字 找到该命令表结构体,调用它的cmd参数 最终还是回到do_xxx run_command就是来解析命令的
run_command分别解析nand命令和bootm命令,nand命令负责把linux内核读到内存中,bootm负责去启动内核镜像,调用do_bootm----->do_bootm_linux,启动内核