U-Boot -第二阶段代码分析
参考文档:
http://blog.chinaunix.net/space.php?uid=20540258&do=blog&cuid=313450
http://www.cnblogs.com/zong-blogs/archive/2011/04/07/2008738.html
http://blog.csdn.net/aaronychen/article/category/445277
在上一篇文章中,我们介绍了u-boot启动的时候汇编语言的部分,当时我们进行了一些简单的初始化,并且为C语言的执行建立的环境(堆栈),现在我们看看当从汇编语言转到C语言的时候执行的第一个函数( start_armboot (),在lib_arm\board.c中),该函数进行了一系列的外设初始化,然后调用main_loop (),根据配置来选择是直接加载Linux内核还是进入等待命令模式。
1、在介绍该函数之前,我们需要看一看几个数据结构,这些是u-boot中几个重要的数据结构:
(1)gd_t结构体
U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义如下:
typedef struct global_data {
bd_t *bd; //与板子相关的结构,见下面
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD //我们一般没有配置这个,这个是frame buffer的首地址
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
/*
* Global Data Flags
*/
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
在global_data.h中U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
根据U-Boot内存使用图中可以计算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
2)bd_t 保存与板子相关的配置参数
bd_t在include/asm-arm/u-boot.h中定义如下:
typedef struct bd_info {
int bi_baudrate; /* 串口通讯波特率 */
unsigned long bi_ip_addr; /* IP地址 */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env; /*环境变量开始地址 */
ulong bi_arch_number; /* unique id for this board开发板的机器码 */
ulong bi_boot_params; /* where this board expects params 内核参数的开始地址*/
struct /* RAM配置信息 */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1个
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
#define bi_env_data bi_env->data
#define bi_env_crc bi_env->crc
U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。
3). 初始化函数列表(以数组的形式)
相关代码在lib-arm/board.c中定义
typedef int (init_fnc_t) (void); /*这是使用typedef定义一个init_fnc_t为函数类型,该函数返回int型,无参数*/
int print_cpuinfo (void); /* test-only */
init_fnc_t *init_sequence[]定义一个init_fnc_t指针类型的数组。简单的说就是定义了个函数指针数组,指向一系列cpu初始化函数。
init_fnc_t *init_sequence[] = { /*全局变量init_sequence的定义,*/
cpu_init, /* basic cpu dependent setup CPU的相关配置,如初始化IRQ/FIQ模式的栈cpu/arm920t/cpu.c*/
board_init, /* basic board dependent setup开发板相关配置,是对板子的初始化,设置MPLL,改变系统时钟,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核启动参数地址,它是开发板相关的函数,比如2410是在:board/smdk2410/smdk2410.c中实现*/
interrupt_init, /* set up exceptions 初始化中断,在cpu/arm920t/s3c24x0/interrupts.c实现*/
env_init, /* initialize environment 初始化环境变量,检查flash上的环境变量是否有效common/env_flash.c 或common/env_nand.c */
init_baudrate, /* initialze baudrate settings 初始化波特率lib_arm/board.c */
serial_init, /* serial communications setup串口初始化串口初始化后我们就可以打印信息了cpu/arm920t/s3c24x0/serial.c */
console_init_f, /* stage 1 init of console 控制台初始化第一阶段common/console.c */
display_banner, /* say that we are here通知代码已经运行到该处,打印U-Boot版本、编译的时间-- lib_arm/board.c */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks 配置可用的内存区,检测系统内存映射,设置内存的起始地址和大小board/smdk2410/smdk2410.c*/
display_dram_config,
NULL,
};
可以看出这里定义了一个指针数组,它的每一个元素都是指针变量,这些指针变量指向的类型为init_fnc_t,在C语言中函数的入口地址就是函数名,所以这里使用一系列函数名来初始化这个数组。
现在来看看到底做了哪些初始化工作
int cpu_init (void)
{
/*
* setup up stacks if necessary
*/
#ifdef CONFIG_USE_IRQ
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
return 0;
}
这个函数cpu/arm920t/cpu.c在中实现,其实这个函数没有做任何工作,因为CONFIG_USE_IRQ这个宏没有定义,那么怎么知道这个宏是否被定义了呢,在使用SourceInsight的搜索功能时,发现有些宏会在很多头文件被定义,而我们又很难判断这些头文件是否被当前的c文件包含了,我使用一个简便的方法,利用编译器的预处理功能,
#ifdef CONFIG_USE_IRQ
#error CONFIG_USE_IRQ_xxxxxx
DECLARE_GLOBAL_DATA_PTR;
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
这样如果这个宏被定义了,那么编译时就会报错输出#error CONFIG_USE_IRQ_xxxxxx,并终止编译.对于smdk2410来说这个宏CONFIG_USE_IRQ没定义,实际上就是把IRQ_STACK_START, FIQ_STACK_START指到RAM中的IRQ stuff区域。
接下来该看开发板基本配置初始化函数: board_init
static inline void delay (unsigned long loops)
{ /*board/smdk2410/smdk2410.c在中实现的延时函数*/
__asm__ volatile ("1:\n"
"subs %0, %1, #1\n"
"bne 1b":"=r" (loops):"0" (loops));
}
/*
* Miscellaneous platform dependent initialisations
*/
int board_init (void) 这个函数board/smdk2410/smdk2410.c在中实现
{
//获取power和clock及GPIO方面的寄存器地址,稍后的操作会对这些寄存器操作,需要看到的是,象S3C24X0_CLOCK_POWER里面的field对象都是按照实际寄存器的地址来安排的
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
/* to reduce PLL lock time, adjust the LOCKTIME register */
//降低PLL的lock time的值,具体含义可参考data sheet
clk_power->LOCKTIME = 0xFFFFFF;
/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
/* some delay between MPLL and UPLL */
delay (4000);
/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
/* some delay between MPLL and UPLL */
delay (8000);
/* 配置每个GPIO的功能,输入输出,等参数 */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;
/* SMDK2410开发板的机器码 */ */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
/* adress of boot parameters内核启动参数地址,运行时在linux内核之下/
gd->bd->bi_boot_params = 0x30000100;
//使能指令cache和数据cache
icache_enable();
dcache_enable();
return 0;
}
现在说一下icache_enable、dcache_enable函数,它定义在cpu/arm920t/cpu.c中,这两个函数是通过修改CP15的c1寄存器来实现的,使能cache很简单,只要把协处理器15的相关位打开就行了,这里来是将c1的I、C位置1,来开启Icache、DCaches。我这里只分析icache_enable,dcache_enable类似。icache_enable具体实现如下:
void icache_enable (void)
{
ulong reg;
reg = read_p15_c1 (); /* get control reg. 获取CP15的c1寄存器值存到reg中*/
cp_delay ();
write_p15_c1 (reg | C1_IC);/*这里将C1寄存器的I、C位置1,来开启Icache、Dcaches,关于CP15的c1寄存器的格式可参看前面u-boot启动第一阶段分析的cpu_init_crit函数部分。
}
这里须要理解的是read_p15_c1与write_p15_c1函数,它们分别在cpu/arm920t/cpu.c中定义如下:
static unsigned long read_p15_c1 (void)
{
unsigned long value;
__asm__ __volatile__(
"mrc p15, 0, %0, c1, c0, 0 @ read control reg\n",%0是参数传递时R0寄存器,其功能是读取CP15的c1寄存器值放到r0中。
: "=r" (value)
:
: "memory");
#ifdef MMU_DEBUG
printf ("p15/c1 is = %08lx\n", value);
#endif
return value; 返回读取CP15的c1寄存器的值
}
/* write to co-processor 15, register #1 (control register) */
static void write_p15_c1 (unsigned long value)
{
#ifdef MMU_DEBUG
printf ("write %08lx to p15/c1\n", value);
#endif
__asm__ __volatile__(
"mcr p15, 0, %0, c1, c0, 0 @ write it back\n" @保存r0的值到控制寄存器CP15的c1寄存器中,因为函数参数传递时,第一个参数都是放在r0寄存器中的。
:
: "r" (value)
: "memory");
read_p15_c1 ();
}
static void cp_delay (void)
{
volatile int i;
/* copro seems to need some delay between reading and writing */
for (i = 0; i < 100; i++);
}
接下来该看初始化函数: interrupt_init,在cpu/arm920t/s3c24x0/interrupts.c实现interrupt_init代码如下:
int timer_load_val = 0;
static ulong timestamp;
static ulong lastdec;
int interrupt_init (void) 初始化timer4相关寄存器,用于产生10ms定时中断信号。
{
S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();//返回定时器配置寄存器地址0x51000000,即TCFG0寄存器地址。
/*这里使用定时器4,定时器4有只有一个内部定器没有输出管脚*/
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00;
if (timer_load_val == 0)
{
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and 15625 @ 50 MHz
*/
timer_load_val = get_PCLK()/(2 * 16 * 100); //PCLK(返回PCLK频率
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val;//设置计数缓存寄存器初始值。
/*设置定时器4手动更新,自动加载模式,并关闭定时器4*/
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
/* auto load, 启动Timer 4 */
timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
timestamp = 0;
return (0);
}
对着datasheet来看这个函数, 实际上这个函数使用timer 4来作为系统clock, 即时钟滴答, 10ms一次,到点就产生一个中断,但由于此时中断还没打开所以这个中断不会响应。
接着看env_init: 由于我们在inculde/configs/smdk2410.h下定义了CFG_ENV_IS_IN_FLASH,因此该函数位于common/env_flash.c下
int env_init(void)
{
#ifdef CONFIG_OMAP2420H4
int flash_probe(void);
if(flash_probe() == 0)
goto bad_flash;
#endif
if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
gd->env_addr = (ulong)&(env_ptr->data);
gd->env_valid = 1;使用include/configs/smdk2410.h配置的环境变量则设置环境变量可用标志
return(0);
}
/* env_ptr在前面定义为env_t *env_ptr = (env_t *)CFG_ENV_ADDR;而CFG_ENV_ADDR被定义在include/configs/smdk2410.h中了,这里判断如果校验正确(即CFG_ENV_ADDR被定义了)则环境变量的存放地址使用smdk2410.h中定义的,否则使用后面的默认的环境变量值default_environment数组*/
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0; 使用默认的环境配置变量则设置环境变量不可用标志
return (0);
}
这个函数主要是在gd里保存环境变量的存放地址。一般使用默认的环境变量值即default_environment数组,它在common/env_commom.c中定义(关于u-boot环境变量更详细的说明请看U-BOOT环境变量实现一文档。地址:http://blog.csdn.net/hxliu_leo/article/details/5315011)
uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
可见环境变量以如下的方式存放在数组中
Name=value
并且以”/0”结束, 而类似CONFIG_BOOTARGS的宏都定义在板子自己的配置文件中即smdk2410.h里。
接下来看init_baudrate,定义在lib_arm/board.c
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);
}
该函数从上面刚初始化好的环境变量列表里找波特率值,如果没有就赋初始值为CONFIG_BAUDRATE。
继续往下看serial_init,cpu/arm920t/s3c24x0/serial.c
int serial_init (void)
{
serial_setbrg ();//设置波特率,停止位等
return (0);
}
void serial_setbrg (void) cpu/arm920t/s3c24x0/serial.c
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
int i;
unsigned int reg = 0;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
/* FIFO enable, Tx/Rx FIFO clear */
uart->UFCON = 0x07;
uart->UMCON = 0x0;
/* Normal,No parity,1 stop,8 bit */
uart->ULCON = 0x3;
/*
* tx=level,rx=edge,disable timeout int.,enable rx error int.,
* normal,interrupt or polling
*/
uart->UCON = 0x245;
uart->UBRDIV = reg;
#ifdef CONFIG_HWFLOW
uart->UMCON = 0x1; /* RTS up */
#endif
for (i = 0; i < 100; i++);
}
上面这个函数对着datasheet看,无非是设置波特率,起始位,检验中断类型等等。
接着看初始化函数:console_init_f,common/console.c
int console_init_f (void)
{
gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT; //设置控制台模式
#endif
return (0);
}
该函数初始化了几个控制台相关的标记。
接着看display_banner: lib_arm/board.c
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string); //打印U-BOOT的版本信息
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end); //打印U-BOOT代码位置
#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);
}
这个函数就是在控制台上打印一些系统信息。
接着看dram_init:
int dram_init (void) 这个函数board/smdk2410/smdk2410.c在中实现
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1; //RAM起始地址
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; //RAM大小
return 0;
}
2410使用2片32MB的SDRAM组成了64MB的内存,接在存储控制器的BANK6,地址空间是0x30000000~0x34000000。在include/configs/smdk2410.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE 分别被定义为0x30000000和0x04000000(64M)。
再看display_dram_config lib_arm/board.c
static int display_dram_config (void)
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i
printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
print_size (gd->bd->bi_dram[i].size, "\n");
}
#else
ulong size = 0;
for (i=0; i
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
呵呵仅仅是打印系统RAM的信息。
接着说初始化函数, 对于Smdk2410来说不存在print_cpuinfo、checkboard函数,
这样整个初始化函数表的函数都看完了,总结一下主要做了如下过程:
1. cpu, borad, interrupt的初始化,包括cache等,这些都于特定板子的配置有关。
2. 环境变量的初始化,
3. 串口,控制台,RAM的初始化,
4. 在控制台上实时的显示系统配置等相关参数。
最后需要说明的是,大部分的配置参数都是预先在include/configs/board_name.h下定义的,因此如果我们要移植我们自己的板子的话,这个文件必不可少,它描述了我们板子的配置情况如CPU型号,RAM大小等。
2、U-Boot第二阶段启动流程
在分析star_armboot()源码前我们先看一下U-Boot第二阶段启动流程。
U-Boot第二阶段流程图如下:
3、start_armboot()源码分析
分析完上述的数据结构,下面来分析start_armboot函数,现在我贴出start_armboot ()的源代码,然后具体的在其中解释一些代码的作用。
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr; /*它是指向指针的指针变量,变量的类型为init_fnc_t,这是一个使用typedef定义的函数类型, 其中init_fnc_ptr将在后面指向一个数组指针*/
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));//计算全局数据结构的地址gd
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t)); //初始化全局数据区为0;
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //为bd_t分配空间,并赋值到gd
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start; //代码段的大小
/* 逐个调用init_sequence数组中的初始化函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* CFG_NO_FLASH 表示没有flash,如果没定义该常量则表示板子上有flash,此时调用flash_init()对其进行初始化.这里是norflash初始化 */
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init (); 详见后分析
display_flash_config (size); //打印flash的信息,仅是其大小;
#endif /* CFG_NO_FLASH */
#ifdef CONFIG_VFD //smdk2410没定义
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# 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
# 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); //初始化malloc区域,_armboot_start=0x33f80000,CFG_MALLOC_LEN=0x30000,mem_malloc_init对 (0x33f80000-0x30000)~(0x33f80000)这段存储区清零
//如果定义了命令和NAND命令,初始化nand
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /*探测NAND flash并根据NAND flash的类型填充相应的数据结构,全局结构体变量nand_dev_desc[0](类型struct nand_chip) */
#endif
#ifdef CONFIG_HAS_DATAFLASH //没定义
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment配置环境变量,重新定位 */ */
env_relocate ();这个函数在common\env_common.c中
#ifdef CONFIG_VFD 没定义
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */
/* IP Address 从环境变量中获取IP地址 */ */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
//从环境变量获取网卡的MAC地址并填充gd结构体变量
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. 对设备初始化,在common/devices.c文件中,会根据配置来完成各个设备的初始化,其中会调用函数drv_system_init(),这个函数对串口设备初始化,然后注册串口设备*/
#ifdef CONFIG_CMC_PU2 //没定义
load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
jumptable_init ();//跳转表初始化,函数在common\exports.c文件中
console_init_r (); /* fully init console as a device函数在common\console.c */
#if defined(CONFIG_MISC_INIT_R) //没有定义
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif
/* enable exceptions */
enable_interrupts ();//使能中断,cpu/arm920t/interrupts.c smdk2410是个空函数void enable_interrupts (void){return;} */
/* Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_CS8900 /*smdk2410没定义*/
cs8900_get_enetaddr (gd->bd->bi_enetaddr); //获取网卡的MAC地址
#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);//读取环境变量loadaddr到全局变量load_addr中
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));//读取环境变量bootfile到全局变量BootFile中
}
#endif /* CFG_CMD_NET */
#ifdef BOARD_LATE_INIT //没定义
board_late_init ();
#endif
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#if defined(CONFIG_NET_MULTI) // smdk2410没定义
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();//直接进入main_loop 该函数在common\main.c中,至此,硬件初始化完成
}
/* NOTREACHED - no way out of command loop except booting */
}
对start_armboot中相关调用的函数分析
这里flash_init定义在board/smdk2410/flash.c:
ulong flash_init (void)
{
int i, j;
ulong size = 0;
for (i = 0; i < CFG_MAX_FLASH_BANKS; i++) {
ulong flashbase = 0;
flash_info[i].flash_id = /*保存flash ID*/
#if defined(CONFIG_AMD_LV400)
(AMD_MANUFACT & FLASH_VENDMASK) |
(AMD_ID_LV400B & FLASH_TYPEMASK);
#elif defined(CONFIG_AMD_LV800)
(AMD_MANUFACT & FLASH_VENDMASK) |
(AMD_ID_LV800B & FLASH_TYPEMASK);
#else
#error "Unknown flash configured"
#endif
/*保存每个flash blank的大小,sector数量,起始地址等信息*/
flash_info[i].size = FLASH_BANK_SIZE;
flash_info[i].sector_count = CFG_MAX_FLASH_SECT;
memset (flash_info[i].protect, 0, CFG_MAX_FLASH_SECT);
if (i == 0)
flashbase = PHYS_FLASH_1;
else
panic ("configured too many flash banks!\n");
/*为每个sector分配不同的大小,作为不同的用途*/
for (j = 0; j < flash_info[i].sector_count; j++) {
if (j <= 3) {
/* 1st one is 16 KB */
if (j == 0) {
flash_info[i].start[j] =
flashbase + 0;
}
/* 2nd and 3rd are both 8 KB */
if ((j == 1) || (j == 2)) {
flash_info[i].start[j] =
flashbase + 0x4000 + (j -
1) *
0x2000;
}
/* 4th 32 KB */
if (j == 3) {
flash_info[i].start[j] =
flashbase + 0x8000;
}
} else {
flash_info[i].start[j] =
flashbase + (j - 3) * MAIN_SECT_SIZE;
}
}
size += flash_info[i].size;
}
/*对flash上保存有RO, RW的地方进行保护,monitor_flash_len = RO + RW的长度*/
flash_protect (FLAG_PROTECT_SET,
CFG_FLASH_BASE,
CFG_FLASH_BASE + monitor_flash_len - 1,
&flash_info[0]);
//对flash上保存有环境变量的地方进行保护
flash_protect (FLAG_PROTECT_SET,
CFG_ENV_ADDR,
CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0]);
return size;
}
该函数就是记录下flash的大小,数量,sector的大小数量等,并对flash上重要的数据进行保护
display_flash_config 定义在lib_arm/board.c:
#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
puts ("Flash: ");
print_size (size, "\n");
}
#endif
很简单,打印flash的大小信息。
mem_malloc_init定义在lib_arm/board.c:
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);
}
这个函数就是保存malloc区域的起始地址,结束地址,并清0这块区域,这块区域在RAM中的具体位置可查看前面的图。
接着看env_relocate
Uboot在完成汇编部分的初始化之后,将跳到start_armboot()去执行,其中便会执行env_relocate()初始化环境变量。
第一步,初始化一个全局指针,它被定义为:
env_t *env_ptr = 0;
第二步,重新初始化函数指针,
static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;
该函数指针原来被初始化为env_get_char_init,现在改为env_get_char_memory。对于nand flash,这两个函数是一样的。
第三步,如果flash没有参数表,则使用默认参数,这里是通过memcpy (env_ptr->data,default_environment, sizeof(default_environment));来加载。
第四步,如果flash上有参数表可用,则从flash上加载,通过env_relocate_spec()来实现:
第五步,gd->env_addr = (ulong)&(env_ptr->data)
即将环境变量的值赋值给全局变量gd->env_addr,这样只要通过这个全局变量就可以访问这些变量了。值得一提的是,字符串数组data里面的变量与变量之间是通过’/0’来分割的。
common/env_common.c:
env_t *env_ptr = 0;
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off);
#ifdef CONFIG_AMIGAONEG3SE //没有定义
enable_nvram();
#endif
/*env_ptr指向存放环境变量的区域*/
#ifdef ENV_IS_EMBEDDED
/*
* The environment buffer is embedded with the text segment,
* just relocate the environment pointer
*/
env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
/*
* We must allocate a buffer for the environment
*/
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);//为环境变量分配内存,env_ptr是全局变量,在下面的env_relocate_spec ()函数中将环境变量读取到env_ptr指向的内存中
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
/*
* After relocation to RAM, we can always use the "memory" functions
*/
env_get_char = env_get_char_memory;
/*如果在env_init中没有初始化合适的环境变量则使用默认的环境变量来作为环境变量值,否则使用env_init中定义好的环境变量值*/
在env_init函数中如果使用环境变量地址是include/configs/smdk2410.h配置的gd->env_valid被初始化为1,即flash上有参数表,如果使用环境变量是默认的环境变量default_environment,则gd->env_valid被初始化为0了。
if (gd->env_valid == 0) { //如果flash没有参数表,则使用默认参数,这里是通过下面
来加载。
#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */
puts ("Using default environment\n\n");
#else
puts ("*** Warning - bad CRC, using default environment\n\n");
SHOW_BOOT_PROGRESS (-1);
#endif
if (sizeof(default_environment) > ENV_SIZE)
{
puts ("*** Error - default environment is too large\n\n");
return;
}
memset (env_ptr, 0, sizeof(env_t));
memcpy (env_ptr->data,
default_environment,
sizeof(default_environment)); /*把默认值存入RAM相应区域*/
#ifdef CFG_REDUNDAND_ENVIRONMENT
env_ptr->flags = 0xFF;
#endif
env_crc_update ();/*更新crc校验值*/
gd->env_valid = 1; /*标记环境变量有效*/
}
else {
env_relocate_spec ();/*如果flash上有参数表可用,则从flash上加载,通过env_relocate_spec()来实现*/
}
gd->env_addr = (ulong)&(env_ptr->data); //填充结构体变量, 即将环境变量的值赋值给全局变量gd->env_addr,这样只要通过这个全局变量就可以访问这些变量了。值得一提的是,字符串数组data里面的变量与变量之间是通过’/0’来分割的。
#ifdef CONFIG_AMIGAONEG3SE //没定义
disable_nvram();
#endif
}
该函数重新分配了一块区域用于存放环境变量,并在gd_t区域保存了这个地址值。
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED) //如果不是使用嵌入参数的形式,即为参数表的形式
ulong total;
int ret;
total = CFG_ENV_SIZE;//参数表大小,包括参数表头部
/*从nand flash 中读取环境变量到全局变量env_ptr指向的内存中,这里需要注意的是CFG_ENV_OFFSET、//CFG_ENV_SIZE这两个宏定义,它们表示从NAND flash中偏移为CFG_ENV_OFFSET处读取大小为CFG_ENV_SIZE字节//的数据,注意这两个宏定义决定了环境变量在nand flash中保存的位置和大小,不能和nand flash 中的其他分区(kernel分区、rootfs分区)相冲突,不然在执行saveenv命令时可能会覆盖其他分区的内容,导致系统无法启动.*/
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr); //读出操作,flash设备为nand_info,偏移为CFG_ENV_OFFSET,读出的大小为total,目标地址由env_ptr所指。
if (ret || total != CFG_ENV_SIZE)
return use_default();//如果读出的长度不对或出错,则使用默认值
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();//如果校验出错,使用默认值
#endif /* ! ENV_IS_EMBEDDED */
}
#endif /* CFG_ENV_OFFSET_REDUND */
该函数实现真正的重定位功能,先从NAND flash中读取环境变量,如果读取成功,并且crc校验正确的话,就使用NAND flash中读取出来的环境变量,否则使用默认的环境变量
此外,uboot的参数表还支持一种被称为CFG_ENV_OFFSET_REDUND的冗余模式,它会在flash上保存两个参数表副本,这样在一个副本出错的时候,还可以从另一个副本中去读取,通过这种方式,提高了数据的安全性。
接着看devices_init: common/devices.c:
int devices_init (void)
{
#ifndef CONFIG_ARM /* already relocated for current ARM implementation */
ulong relocation_offset = gd->reloc_off;
int i;
/* relocate device name pointers */
for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
relocation_offset);
}
#endif
/* Initialize the list 创建一个保存device的列表*/*/
devlist = ListCreate (sizeof (device_t));
if (devlist == NULL) {
eputs ("Cannot initialize the list of devices!\n");
return -1;
}
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) //没定义
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
#endif
#ifdef CONFIG_LCD//没定义
drv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE) //没定义
drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARD//没定义
drv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFER//没定义
drv_logbuff_init ();
#endif
drv_system_init ();
#ifdef CONFIG_SERIAL_MULTI//没定义
serial_devices_init ();
#endif
#ifdef CONFIG_USB_TTY//没定义
drv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLE //没定义
drv_nc_init ();
#endif
return (0);
}
这个函数实际上就是根据板子的配置初始化各种设备,并调用device_register()注册到系统中去,在这个中函数仅对串口设备初始化,然后注册串口设备.这里我们以drv_system_init为例解释一下,定义也在common/devices.c,其他代码类似。
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); /*把该设备注册进系统中去,即把dev添加进上面创建的设备列表中去*/
#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
}
该函数注册了一个串口设备和一个空设备(根据配置而定)。其他的设备初始化函数以此大同小异,主要就是初始化好相关设备的设备信息,并注册到系统中去,详细代码大家可以自己去分析。
接下来看jumptable_init
common/exports.c
void jumptable_init (void)
{
int i;
/*分配一块buffer用于存放跳转函数地址*/
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy; /*默认跳转函数地址,是一个空函数的地址*/
/*为每种功能定义各自的跳转函数*/
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
gd->jt[XF_get_timer] = (void *) get_timer;
gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
gd->jt[XF_udelay] = (void *) udelay;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif /* I386 || PPC */
#if (CONFIG_COMMANDS & CFG_CMD_I2C)
gd->jt[XF_i2c_write] = (void *) i2c_write;
gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif /* CFG_CMD_I2C */
}
static void dummy(void)
{
}
以看出该函数主要就是为不同的功能安装了不同的功能函数。
继续看console_init_r()函数
common/console.c
/* Called after the relocation - use desired console functions */
int console_init_r (void)
{
/*1. 首先获取由device_init里注册的input,output设备*/
device_t *inputdev = NULL, *outputdev = NULL;
int i, items = ListNumItems (devlist);
#ifdef CONFIG_SPLASH_SCREEN //没定义
/* suppress all output if splash screen is enabled and we have
a bmp to display */
if (getenv("splashimage") != NULL)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
#ifdef CONFIG_SILENT_CONSOLE //没定义
/* Suppress all output if "silent" mode requested */
if (gd->flags & GD_FLG_SILENT)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
/* Scan devices looking for input and output devices */
/*寻找系统上存在的输入,输出设备,别忘了上面注册的串口设备就是输入,输出设备*/
for (i = 1;
(i <= items) && ((inputdev == NULL) || (outputdev == NULL));
i++
) {
device_t *dev = ListGetPtrToItem (devlist, i);
if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
inputdev = dev; /*找到输入设备,参考drv_system_init */
}
if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
outputdev = dev; /*找到输出设备,参考drv_system_init */
}
}
/* Initializes output console first */
/*由drv_system_init可知,我们可以找到输入,输出设备,而且都是同一个serial设备*/
if (outputdev != NULL) {
console_setfile (stdout, outputdev); /*设置标准输出设备*/
console_setfile (stderr, outputdev); /*设置标准的错误输出设备*/
}
/* Initializes input console */
if (inputdev != NULL) {
console_setfile (stdin, inputdev); /*设置标准输入设备*/
}
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed 设置初始化完成标记*/
#ifndef CFG_CONSOLE_INFO_QUIET
/* Print information 打印相关设备信息*/
puts ("In: ");
if (stdio_devices[stdin] == NULL) {
puts ("No input devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdin]->name);
}
puts ("Out: ");
if (stdio_devices[stdout] == NULL) {
puts ("No output devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdout]->name);
}
puts ("Err: ");
if (stdio_devices[stderr] == NULL) {
puts ("No error devices available!\n");
} else {
printf ("%s\n", stdio_devices[stderr]->name);
}
#endif /* CFG_CONSOLE_INFO_QUIET */
/* Setting environment variables 设置环境变量*/
for (i = 0; i < 3; i++) {
setenv (stdio_names[i], stdio_devices[i]->name);
}
#if 0
/* If nothing usable installed, use only the initial console */
if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL))
return (0);
#endif
return (0);
}
该函数主要是设置好了标准输入,标准输出,标准错误输出设备,并定义好相关输入,输出函数,以使后面的如puts(),printf()等函数可以运行,这个应该不陌生的。 为了一解疑惑我们可以继续分析下去:
static int console_setfile (int file, device_t * dev) common/console.c
{
int error = 0;
if (dev == NULL)
return -1;
switch (file) {
case stdin:
case stdout:
case stderr:
/* Start new device */
if (dev->start) {
error = dev->start ();/*在drv_system_init下没定义这个函数*/
/* If it's not started dont use it */
if (error < 0)
break;
}
/* Assign the new device (leaving the existing one started) */
stdio_devices[file] = dev; /*保存标准输入,标准输出,标准错误输出设备*/
/*
* Update monitor functions
* (to use the console stuff by other applications)
*/
设置好标准输入,标准输出,标准错误输出设备的输入,输出函数,可以从drv_system_init下查到
switch (file) {
case stdin:
gd->jt[XF_getc] = dev->getc;
gd->jt[XF_tstc] = dev->tstc;
break;
case stdout:
gd->jt[XF_putc] = dev->putc;
gd->jt[XF_puts] = dev->puts;
gd->jt[XF_printf] = printf;
break;
}
break;
default: /* Invalid file ID */
error = -1;
}
return error;
}
这个函数就是初始化好标准输入,标准输出,标准错误输出设备,这以后就可以调用如puts,printf等函数了. 我们以puts为例继续分析:
common/console.c:
void puts (const char *s)
{
#ifdef CONFIG_SILENT_CONSOLE
if (gd->flags & GD_FLG_SILENT)
return;
#endif
if (gd->flags & GD_FLG_DEVINIT) {//这个标记前面设置过了
/* Send to the standard output */
fputs (stdout, s); /*就是调用这个函数*/
} else {
/* Send directly to the handler */
serial_puts (s);
}
}
void fputs (int file, const char *s)
{
if (file < MAX_FILES)
stdio_devices[file]->puts (s); 这里就是调用我们初始化时设置的了即serial_puts()函数,可以在drv_system_init查到
}
serial_puts函数就是和具体设备相关了,对于smdk2410的代码如下
cpu/arm920t/s3c24x0/serial.c:
void serial_puts (const char *s)
{
while (*s) {
serial_putc (*s++);
}
}
void serial_putc (const char c) cpu/arm920t/s3c24x0/serial.c
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
#ifdef CONFIG_MODEM_SUPPORT
if (be_quiet)
return;
#endif
/* wait for room in the tx FIFO */
while (!(uart->UTRSTAT & 0x2));
#ifdef CONFIG_HWFLOW
/* Wait for CTS up */
while(hwflow && !(uart->UMSTAT & 0x1))
;
#endif
uart->UTXH = c;
/* If \n, also do \r */
if (c == '\n')
serial_putc ('\r');
}
这些函数对照着datasheet就很好理解了。
printf函数最终也是调用puts函数。
至此我们对打印的来龙去脉了解了。
接下来主要分析main_loop(),common/main.c
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER /*smdk2410没定义*/
static char lastcommand[CFG_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#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) /*smdk2410没定义*/
ulong bmp = 0; /* default bitmap */
extern int trab_vfd (ulong bitmap);
#ifdef CONFIG_MODEM_SUPPORT /*smdk2410没定义*/
if (do_mdm_init)
bmp = 1; /* alternate bitmap */
#endif
trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */
#ifdef CONFIG_BOOTCOUNT_LIMIT /*smdk2410没定义*/
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 /*smdk2410没定义*/
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 /*smdk2410没定义*/
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CFG_HUSH_PARSER /*smdk2410没定义*/
u_boot_hush_start ();
#endif
#ifdef CONFIG_AUTO_COMPLETE /*smdk2410没定义*/
install_auto_complete();
#endif
#ifdef CONFIG_PREBOOT /*smdk2410没定义*/
if ((p = getenv ("preboot")) != NULL) {
# ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410没定义*/
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER /*smdk2410没定义*/
run_command (p, 0);
# else
parse_string_outer(p, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410没定义*/
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
#endif /* CONFIG_PREBOOT */
#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);
# ifdef CONFIG_BOOT_RETRY_TIME
init_cmd_timeout ();
# endif /* CONFIG_BOOT_RETRY_TIME */
#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 */
s = getenv ("bootcmd"); /*smdk2410下这个环境变量是空的*/
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# 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
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);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
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 ("
else
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
#endif /*CFG_HUSH_PARSER*/
}
该函数比较长,但核心就是不停的从串口获取命令并解析执行此命令,这个功能就在函数尾部的那个死循环里, 我们重点就是分析这个循环。
先看readline()
common/main.c
/****************************************************************************/
/*
* Prompt for input and read a line.
* If CONFIG_BOOT_RETRY_TIME is defined and retry_time >= 0,
* time out when time goes past endtime (timebase time in ticks).
* Return: number of read characters
* -1 if break
* -2 if timed out
*/上面的注释说的很清楚了,就是提示用户输入命令,并读取这个命令
int readline (const char *const prompt)
{
#ifdef CONFIG_CMDLINE_EDITING /*sdmk2410没定义*/
char *p = console_buffer; /* console_buffer 是个全局变量*/
unsigned int len=MAX_CMDBUF_SIZE;
int rc;
static int initted = 0;
if (!initted) {
hist_init();
initted = 1;
}
puts (prompt);
rc = cread_line(p, &len);
return rc < 0 ? rc : len;
#else
char *p = console_buffer;
int n = 0; /* buffer index */
int plen = 0; /* prompt length */
int col; /* output column cnt */
char c;
/* print prompt */
if (prompt) { /*如果要输出提示符,则输出*/
plen = strlen (prompt);
puts (prompt);
}
col = plen;
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME /*sdmk2410没定义*/
while (!tstc()) { /* while no incoming data */
if (retry_time >= 0 && get_ticks() > endtime)
return (-2); /* timed out */
}
#endif
WATCHDOG_RESET(); /* Trigger watchdog, if needed */
#ifdef CONFIG_SHOW_ACTIVITY /*sdmk2410没定义*/
while (!tstc()) {
extern void show_activity(int arg);
show_activity(0);
}
#endif
c = getc();/*从串口获取用户输入的命令*/
/*
* Special character handling
*/ /*下面这个switch就是处理不同的字符了*/
switch (c) {
case '\r': /* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
return (p - console_buffer); /*输入结束*/
case '\0': /* nul */ /*忽略,继续*/
continue;
case 0x03: /* ^C - break linux下的ctrl+c功能*/
console_buffer[0] = '\0'; /* discard input */
return (-1);
case 0x15: /* ^U - erase line删除 */
while (col > plen) {
puts (erase_seq);
--col;
}
p = console_buffer;
n = 0;
continue;
case 0x17: /* ^W - erase word删除 */
p=delete_char(console_buffer, p, &col, &n, plen);
while ((n > 0) && (*p != ' ')) {
p=delete_char(console_buffer, p, &col, &n, plen);
}
continue;
case 0x08: /* ^H - backspace */
case 0x7F: /* DEL - backspace */
p=delete_char(console_buffer, p, &col, &n, plen); 删除
continue;
default: /*获取常规字符*/
/*
* Must be a normal character then
*/
if (n < CFG_CBSIZE-2) {
if (c == '\t') { /* expand TABs */
#ifdef CONFIG_AUTO_COMPLETE
/* if auto completion triggered just continue */
/*自动补全功能,熟悉linux的肯定知道*/
*p = '\0';
if (cmd_auto_complete(prompt, console_buffer, &n, &col)) {
p = console_buffer + n; /* reset */
continue;
}
#endif
puts (tab_seq+(col&07));
col += 8 - (col&07);
} else {
++col; /* echo input */
putc (c);
}
*p++ = c; /*把字符保存在buffer中*/
++n;
} else { /* Buffer full */
putc ('\a');
}
}
}
#endif /* CONFIG_CMDLINE_EDITING */
}
上面这个函数就是提示用户输入,然后一直等待用户输入,指到用户输入完成, 然后获取用户数据,并存入全局变量console_buffer中。
再来回顾下那个for循环, 核心中的核心就是run_command函数了,一猜就知道是用户执行所有用户下达命令的函数
/****************************************************************************
* returns:
* 1 - command executed, repeatable
* 0 - command executed but not repeatable, interrupted commands are
* always considered not repeatable
* -1 - not executed (unrecognized, bootd recursion or too many args)
* (If cmd is NULL or "" or longer than CFG_CBSIZE-1 it is
* considered unrecognized)
*
* WARNING:
*
* We must create a temporary copy of the command since the command we get
* may be the result from getenv(), which returns a pointer directly to
* the environment data, which may change magicly when the command we run
* creates or modifies environment variables (like "bootp" does).
*/
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CFG_CBSIZE];
char *str = cmdbuf;
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
#ifdef DEBUG_PARSER
printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */
puts ("\"\n");
#endif
clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd) { /*先是对命令的有效性进行检测*/
return -1; /* empty command */
}
if (strlen(cmd) >= CFG_CBSIZE) {
puts ("## Command too long!\n");
return -1;
}
strcpy (cmdbuf, cmd); /*备份command*/
/* Process separators and check for invalid
* repeatable commands
*/
#ifdef DEBUG_PARSER
printf ("[PROCESS_SEPARATORS] %s\n", cmd);
#endif
while (*str) { /*看前面的定义,str指向cmdbuf */
/*
* Find separator, or string end
* Allow simple escape of ';' by writing "\;"
*/ /*下面这个for是对u-boot的特殊语法的解析,这里就不分析了*/
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;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
#ifdef DEBUG_PARSER
printf ("token: \"%s\"\n", token);
#endif
/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) { /*获取参数*/
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) { /*获取对应的command*/
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) { /*检测command语法是否正确*/
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}
#if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
printf ("[%s]\n", finaltoken);
#endif
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif /* CFG_CMD_BOOTD */
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { /*执行具体的command*/
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc ())
return 0; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
这个函数主要是对用户输入的命令进行语法分析,从中获取命令,参数等信息,并查找一张系统保存的命令表,找到该命令对应的处理函数。并调用它来处理这个命令。
下面先详细分析这张系统的命令表是如何生成的,以添加一个命令为例,来详细说明。
U-Boot添加命令
一、相关数据定义说明
首先看的是命令表中存的每个命令的属性,结构体cmd_tbl_t在include/command.h中定义如下:
struct cmd_tbl_s {
char *name; /* Command Name命令名 */
int maxargs; /* maximum number of arguments最大参数个数 */
int repeatable; /* autorepeat allowed? 是否自动重复 */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 响应函数 */
char *usage; /* Usage message (short) 简短的帮助信息 */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) 较详细的帮助信息*/
#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
};
内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。若在include/configs/smdk2410.h中定义了CFG_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示 usage和help字段的内容,否则就只显示usage字段的内容。
接着看如何定义命令:
include/command.h
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
凡是带有__attribute__ ((unused,section (".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。
#ifdef CFG_LONGHELP
/*每个命令就用这个宏来定义,并加进系统命令表中*/
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串。它定义一个command变量,并把它放入".u_boot_cmd"节中*/
#else /* no long help info */
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
#endif /* CFG_LONGHELP */
#endif /* __COMMAND_H */
U_BOOT_CMD宏在include/command.h中定义,
其中U_BOOT_CMD命令格式如下:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
各个参数的意义如下:
name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
maxargs:命令的最大参数个数
rep:是否自动重复(按Enter键是否会重复执行)
cmd:该命令对应的响应函数
usage:简短的使用说明(字符串)
help:较详细的使用说明(字符串)
二、添加定义一个“hello”命令
1.建立common/cmd_hello.c
习惯上通用命令源代码放在common目录下,并且习惯以“cmd_<命令名>.c”为文件名。
2.定义“hello”命令
在cmd_hello.c中使用如下的代码定义“hello”命令:
U_BOOT_CMD(
hello, 3, 0, do_hello,
"hello \n",
" - test cmd..."
);
这定义了一个名字为hello的command, 对应的函数为do_hello, 也就是说如果用户输入命令: hello,u-boot将执行do_hello函数。
至于为什么要用这种方式来定义一个命令列表,大家可以看一下这个文件:
doc/README.commands:
**** Behinde the scene ******
The structure created is named with a special prefix (__u_boot_cmd_)
and placed by the linker in a special section.
This makes it possible for the final link to extract all commands
compiled into any object code and construct a static array so the
command can be found in an array starting at __u_boot_cmd_start.
If a new board is defined do not forget to define the command section
by writing in u-boot.lds ($(TOPDIR)/board/boardname/u-boot.lds) these
3 lines:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
创建的结构用一个特殊的前缀命名(__u_boot_cmd_)并放置在一个特殊的连接区。
这使得所有命令被编译成目标作为一个静态数组结构,使命令在__u_boot_cmd_start开始的数组中可以找到。
如果定义一个新的板子,不要忘记定义的命令段,主要是在u-boot.lds ($(TOPDIR)/board/boardname/u-boot.lds)文件中添加下面三行。
在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段:
. = .;
__u_boot_cmd_start = .; /*将 __u_boot_cmd_start指定为当前地址 */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; /* 将__u_boot_cmd_end指定为当前地址 */
这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。这样只要将U-Boot所有命令对应的 cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在 __u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了。
因此“hello”命令的定义经过宏展开后如下:
cmd_tbl_t __u_boot_cmd_hello __attribute__ ((unused,section (".u_boot_cmd"))) = {hello, 3, 0, do_hello, " hello \n ", " - test cmd..."}
实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。编译器将该结构体放在“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的cmd_tbl_t类型结构体。
3、建立common/cmd_hello.c
习惯上通用命令源代码放在common目录下,并且习惯以“cmd_<命令名>.c”为文件名。
4.实现命令的函数
在cmd_hello.c中添加“hello”命令的响应函数的实现。具体的实现代码略:
int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* 实现代码略 */
}
5.将common/cmd_hello.c编译进u-boot.bin
在common/Makefile中加入如下代码:
COBJS = main.o ACEX1K.o altera.o bedbug.o circbuf.o \
cmd_ace.o cmd_autoscript.o cmd_hello.o \
重新编译下载U-Boot就可以使用hello命令
6.对于u-boot2011.03将common/cmd_hello.c编译进u-boot.bin
在common/Makefile中加入如下代码:
COBJS-$(CONFIG_BOOT_MENU) += cmd_hello.o
在include/configs/smdk2410.h加入如代码:
#define CONFIG_BOOT_MENU 1
重新编译下载U-Boot就可以使用menu命令
======================================================================
hello命令执行的过程:
(1)在U-Boot中输入“hello”命令执行时,U-Boot接收输入的字符串“hello”,传递给run_command函数。
run_command函数在common/main.c中定义。
(2)run_command函数调用common/command.c中实现的find_cmd函数在__u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回hello命令的cmd_tbl_t结构。
find_cmd函数在common/command.c定义。
(3)然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用hello命令的响应函数do_hello,从而完成了命令的执行。
下面我们就以引导linux内核的命令bootm为例,说一下u-boot到linux过渡及参数传递的整个过程, common/cmd_bootm.c:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);
从注释中可以看到这个命令的作用是从memory中引导application,我们这里就是引导linux内核,前提就是linux kernel已经在memory中了。这条命令的处理函数就是do_bootm()。
ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong addr;
ulong data, len, checksum;
ulong *len_ptr;
uint unc_len = CFG_BOOTM_LEN;
int i, verify;
char *name, *s;
int (*appl)(int, char *[]);
image_header_t *hdr = &header;
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
read_dataflash(addr, sizeof(image_header_t), (char *)&header);
} else
#endif
memmove (&header, (char *)addr, sizeof(image_header_t));
if (ntohl(hdr->ih_magic) != IH_MAGIC) {
#ifdef __I386__ /* correct image format not implemented yet - fake it */
if (fake_header(hdr, (void*)addr, -1) != NULL) {
/* to compensate for the addition below */
addr -= sizeof(image_header_t);
/* turnof verify,
* fake_header() does not fake the data crc
*/
verify = 0;
} else
#endif /* __I386__ */
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
SHOW_BOOT_PROGRESS (2);
data = (ulong)&header;
len = sizeof(image_header_t);
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
len = ntohl(hdr->ih_size) + sizeof(image_header_t);
read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
addr = CFG_LOAD_ADDR;
}
#endif
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
#if defined(__PPC__)
if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-4);
return 1;
}
SHOW_BOOT_PROGRESS (5);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[0]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
for (i=1; len_ptr[i]; ++i)
data += 4;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-5);
return 1;
}
SHOW_BOOT_PROGRESS (6);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
#ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts();
/* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
char buf[32];
sprintf(buf, "%lX", len);
setenv("filesize", buf);
return 0;
}
appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
(*appl)(argc-1, &argv[1]);
return 0;
case IH_TYPE_KERNEL:
case IH_TYPE_MULTI:
/* handled below */
break;
default:
if (iflag)
enable_interrupts();
printf ("Can't boot image type %d\n", hdr->ih_type);
SHOW_BOOT_PROGRESS (-8);
return 1;
}
SHOW_BOOT_PROGRESS (8);
/*看到了吧,u-boot是如何支持多个操作系统引导的*/
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify); /*linux引导的函数*/
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#if (CONFIG_COMMANDS & CFG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
}
SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
return 1;
}
U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的是do_bootm函数。do_bootm()函数很大, 但是大部分代码都很简单,就是做一些检测,看是否是正确的image,做的检测有:magic number, crc校验,体系结构是否正确等等,如果是压缩的还会先解压缩(这里的压缩和linux编译出来的压缩没关系,应该是application是否被gzip等压缩过的概念)。对于Linux内核,最后do_bootm函数会调用do_bootm_linux函数来设置标记列表和启动内核。do_bootm_linux函数在lib_arm/armlinux.c中定义如下:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params); /*定义一个函数指针*/
image_header_t *hdr = &header; /*linux image的头*/
bd_t *bd = gd->bd; /*用来存放传递给linux kernel参数的地址*/
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");/* U-Boot环境变量bootargs */
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); /* 获取内核加载地址 */
/*
* Check if there is an initrd image
*/
// 用户自定义了initrd之后需要加载进来
// 整个过程需要进行头部以及整个数据内部校验
// 类似于内核的加载校验。
if (argc >= 3) { /*存在initrd image*/
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16); /*获取地址*/
printf ("## Loading Ramdisk Image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
memcpy (&header, (char *) addr,
sizeof (image_header_t)); /*获取initrd的头*/
if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (unsigned char *) data, len) != checksum) { /*合法性检测*/
printf ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
if (verify) { /*合法性检测*/
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (unsigned char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK\n");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) { /*合法性检测*/
printf ("No Linux ARM Ramdisk Image\n");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */
/*
* Now check if we have a multifile image
*/
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
上面的代码主要是检查是否有initrd image,如有则记住它在内存的地址。
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
if (data) { /*保存initrd的地址*/
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
/*下面这些实际上就是设置好参数*/
// 在smdk2410.h文件中定义了如下宏
// #define CONFIG_CMDLINE_TAG
// #define CONFIG_SETUP_MEMORY_TAGS
// #define CONFIG_INITRD_TAG
// 根据上面不同的宏加载不同的TAG
// 注意的是必须定义CONFIG_CMDLINE_TAG和CONFIG_SETUP_MEMORY_TAGS
// 除非内核已经根据系统初始化了这些值, 否则必须定义, 不定义将导致无法启动.
#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); /* 设置ATAG_CORE标志 */
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); /* 设置内存标记 */
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline); /* 设置命令行标记 */
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); /* 设置ATAG_NONE标志 */
#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 (); /* 启动内核前对CPU作最后的设置 */
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); /*正式跳转到linux kernel*/
}
其中的setup_start_tag,setup_memory_tags,setup_end_tag函数在lib_arm/armlinux.c中定义如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; /* 内核的参数的开始地址 */
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
int i;
/*设置一个内存标记 */
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。
#ifdef CONFIG_SERIAL_TAG
void setup_serial_tag (struct tag **tmp)
{
struct tag *params = *tmp;
struct tag_serialnr serialnr;
void get_board_serial(struct tag_serialnr *serialnr);
get_board_serial(&serialnr);
params->hdr.tag = ATAG_SERIAL;
params->hdr.size = tag_size (tag_serialnr);
params->u.serialnr.low = serialnr.low;
params->u.serialnr.high= serialnr.high;
params = tag_next (params);
*tmp = params;
}
#endif
setup_serial_tag函数传进来的参数是¶ms,而params在setup_start_tag()里已经被初始化为
params = (struct tag *) bd->bi_boot_params;
因此该函数对params里变量的赋值实际上就是在对bd->bi_boot_params下赋值,因此bi_boot_params地址处存放了所有相关的参数。最后这个地址被传给了linux kernel。
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。
U-Boot设置好标记列表后就要调用内核了。但调用内核前,CPU必须满足下面的条件:
(1) CPU寄存器的设置
Ø r0=0
Ø r1=机器码
Ø r2=内核参数标记列表在RAM中的起始地址
(2) CPU工作模式
Ø 禁止IRQ与FIQ中断
Ø CPU为SVC模式
(3) 使数据Cache与指令Cache失效
do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。cleanup_before_linux函数在cpu/arm920t/cpu.c中定义:
int cleanup_before_linux (void)
{
/*
* this function is called just before we call linux
* it prepares the processor for linux
*
* we turn off caches etc ...
*/
unsigned long i;
disable_interrupts (); /* 禁止FIQ/IRQ中断 */
/* turn off I/D-cache /*关掉数据和指令的cache*/ */
asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
i &= ~(C1_DC | C1_IC);
asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));
/* flush I/D-cache 复位/刷新数据和指令的cache */
i = 0;
asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));
return (0);
}
由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。
do_bootm_linux中:
void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))images->ep;
theKernel (0, machid, bd->bi_boot_params);
第2行代码将内核的入口地址“images->ep”强制类型转换为函数指针。根据ATPCS规则,函数的参数个数不超过4 个时,使用r0~r3这4个寄存器来传递参数。因此第3行的函数调用则会将0放入r0,机器码machid放入r1,内核参数地址 bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址。
到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。后面会再分析linux启动顺序,这样可以有个衔接。