以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、start_armboot函数的简介
uboot的第一阶段,start.S文件中初始化SoC内部的硬件,然后长跳转到start_armboot 函数中。
uboot的第二阶段,start_armboot函数始化SoC外部硬件,比如inand、网卡芯片等硬件,以及设置uboot本身内容,比如命令行、环境变量与基本命令等内容。
start_armboot函数位于lib_arm/board.c文件中,删除条件编译后的代码如下:
void start_armboot (void)
{init_fnc_t **init_fnc_ptr;char *s;int mmc_exist = 0;#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)ulong size;
#endif/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ulong gd_base;gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);gd = (gd_t*)gd_base;
#endif/* compiler optimization barrier needed for GCC >= 3.4 */__asm__ __volatile__("": : :"memory");memset ((void*)gd, 0, sizeof (gd_t));gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));memset (gd->bd, 0, sizeof (bd_t));monitor_flash_len = _bss_start - _armboot_start;for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}}#ifndef CFG_NO_FLASH/* configure available FLASH banks */size = flash_init ();display_flash_config (size);
#endif /* CFG_NO_FLASH *//* armboot_start is defined in the board-specific linker script */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#endif//******************************//
// Board Specific
// #if defined(CONFIG_SMDKXXXX)
//******************************//#if defined(CONFIG_X210)#if defined(CONFIG_GENERIC_MMC)puts ("SD/MMC: ");mmc_exist = mmc_initialize(gd->bd);if (mmc_exist != 0){puts ("0 MB\n");}#endif#if defined(CONFIG_CMD_NAND)puts("NAND: ");nand_init();#endif#endif /* CONFIG_X210 *//* initialize environment */env_relocate ();/* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");/* 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;}}devices_init (); /* get the devices list going. */jumptable_init ();
#if !defined(CONFIG_SMDK6442)console_init_r (); /* fully init console as a device */
#endif/* enable exceptions */enable_interrupts ();#ifdef CONFIG_DRIVER_CS8900cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif/* 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_INITboard_late_init ();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)puts ("Net: ");
#endifeth_initialize(gd->bd);
#endif#if defined(CONFIG_CMD_IDE)puts("IDE: ");ide_init();
#endif/****************lxg added**************/
#ifdef CONFIG_MPADextern int x210_preboot_init(void);x210_preboot_init();
#endif
/****************end**********************//* check menukey to update from sd */extern void update_all(void);if(check_menu_update_from_sd()==0)//update mode{puts ("[LEFT DOWN] update mode\n");run_command("fdisk -c 0",0);update_all();}elseputs ("[LEFT UP] boot mode\n");/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop ();}/* NOTREACHED - no way out of command loop except booting */
}
二、Start_armboot函数的解析
1、init_fnc_t类型
void start_armboot (void)
{init_fnc_t **init_fnc_ptr;char *s;int mmc_exist = 0;
(1)init_fnc_t 类型是函数类型,定义如下,它表示该类型的函数传参为空,返回值为int类型。
typedef int (init_fnc_t) (void)
(2)init_fnc_ptr是一个二重函数指针变量,指向一个函数指针数组。
2、gd_t*类型变量gd
/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ulong gd_base;gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);gd = (gd_t*)gd_base;
#endif
(1)在include/asm-arm/global_data.h中定义了一个gd_t*类型变量gd,如下所示。
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
- 用register修饰表示这个变量要尽量放到寄存器中。
- 用volatile修饰表示这个变量是可变的,防止编译器对代码进行优化。
- 结构体gd_t的介绍见博文gd_t 和 bd_t 简介,uboot使用gd_t结构体来存储全局数据区的数据。
- asm("r8")是gcc支持的一种语法,意思是把gd放到寄存器r8中。
总之,DECLARE_GLOBAL_DATA_PTR这个宏,定义了一个存放在寄存器r8中、指向gd_t类型的全局指针变量gd。由于它在程序中经常被访问,因此放在寄存器中以提高效率。通过gd这个指针变量,我们可以访问全局数据区。
(2)指针gd指向了gd_base这个地址,也就是说gd_base这个地址是全局数据区的开始地址。
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
- CFG_UBOOT_BASE=0x33e0_0000。
- CFG_UBOOT_SIZE=2*1024*1024(即2MB大小)=0x0020_0000。
- CFG_MALLOC_LEN=CFG_ENV_SIZE + 896*1024=0x4000+0x000e_0000=0x000e_4000。
- CFG_STACK_SIZE =512*1024(即512KB大小)=0x0008_0000。
- 因此根据计算结果,可知gd_base所表示的地址是 0x33F1C000-sizeof(gd_t)。
示意图如下:
3、将gd和gd->bd两指针指向的内存初始化为0
/* compiler optimization barrier needed for GCC >= 3.4 */__asm__ __volatile__("": : :"memory");memset ((void*)gd, 0, sizeof (gd_t));gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));memset (gd->bd, 0, sizeof (bd_t));
(1)这里有点奇怪,gd->bd为何不是 gd->bd=(bd_t*)gd呢?毕竟gd_t结构体类型的第一个成员就是bd_t结构体类型,按理gd的地址和gd->bd的地址应该是一样的。难道结构体中的成员不是按先后顺序存放在内存上的吗?待解决!
(2)“__asm__ __volatile__("": : :"memory");”,这句代码是什么意思?
4、遍历执行init_sequence(各种硬件的初始化)
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}}
(1)init_sequence是一个函数指针数组,数组中存储了很多函数指针,这些函数指针所指向的函数都是init_fnc_t类型的,都是board级别(即SoC外部)的各种硬件初始化函数。这些函数的特征是接收参数是void类型,返回值是int,函数执行正确时返回0,不正确时返回-1。
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 */
#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,
#endifdram_init, /* configure available RAM banks */display_dram_config,NULL,
};
(2)init_fnc_ptr是一个二重函数指针,因此可以指向init_sequence这个函数指针数组。
(3)遍历时会检查函数的返回值,任何一个函数返回值不等于0则会执行hang函数。hang函数内部只有一行代码,即while(1)。因此uboot启动过程中初始化板级硬件时,不能出任何错误,否则整个启动流程就会终止,除了重启开发板没有任何办法。
下面开始分析init_sequence这个函数指针数组里面的函数。
4.1 cpu_init函数
该函数位于cpu/s5pc11x/cpu.c文件中,内容如下:
int cpu_init (void)
{/** setup up stacks if necessary*/
#ifdef CONFIG_USE_IRQ //没有定义这个宏IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endifreturn 0;
}
因为cpu相关的初始化已经在start.S文件完成,所以这里什么也没有做。
4.2 board_init函数
该函数位于board/samsung/x210/x210.c文件中,内容如下:
int board_init(void)
{DECLARE_GLOBAL_DATA_PTR;//这里定义了一个指针变量gd#ifdef CONFIG_DRIVER_DM9000dm9000_pre_init();//初始化dm900网卡
#endif//对gd->bd中的机器码、启动参数的地址进行赋值gd->bd->bi_arch_number = MACH_TYPE;//机器码gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);//uboot给内核传递的启动参数的首地址return 0;
}
此函数初始化了dm9000网卡,并且对gd->bd中的机器码、启动参数的地址进行赋值。
(1)dm9000_pre_init函数是dm9000网卡的初始化函数。如果要移植网卡,主要工作就在这里。这个函数主要完成网卡的GPIO和端口的配置,而不是驱动。
static void dm9000_pre_init(void)
{unsigned int tmp;//CONFIG_DRIVER_DM9000宏在x210_sd.h中有定义,用来配置开发板的网卡
#if defined(DM9000_16BIT_DATA)SROM_BW_REG &= ~(0xf << 4);SROM_BW_REG |= (1<<7) | (1<<6) | (1<<5) | (1<<4);
#elseSROM_BW_REG &= ~(0xf << 4);SROM_BW_REG |= (0<<6) | (0<<5) | (0<<4);
#endifSROM_BC1_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0));//uboot//SROM_BC1_REG = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0));//kerneltmp = MP01CON_REG;tmp &=~(0xf<<4);tmp |=(2<<4);MP01CON_REG = tmp;
}
(2)追踪宏定义可知gd->bd->bi_boot_params=0x30000100,它表示内核启动参数的首地址。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);//uboot给内核传递的启动参数的首地址#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define MEMORY_BASE_ADDRESS 0x30000000
(3)文件include/configs/x210_sd.h定义了MACH_TYPE这个宏,其值是2456,表示代表X210开发板的机器码。将来移植到在X210开发板的Linux内核,其机器码必须是2456,否则启动不起来。
4.3 interrupt_init函数
该函数位于cpu/s5pc11x/interrupts.c文件中,内容如下:
int interrupt_init(void)
{S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();/* use PWM Timer 4 because it has no output *//* prescaler for Timer 4 is 16 */timers->TCFG0 = 0x0f00;//设置预分频15+1=16if (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 @ 66 MHz*/timer_load_val = get_PCLK() / (16 * 100);//设置10ms}/* load value for 10 ms timeout */lastdec = timers->TCNTB4 = timer_load_val;/* auto load, manual update of Timer 4 */timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;/* auto load, start Timer 4 */timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;timestamp = 0;return (0);
}
(1)这个函数实际是用来初始化定时器Timer4,函数名貌似挂羊头卖狗肉?
(2)S5PC11X_TIMERS结构体类型,其成员是与时钟有关的所有寄存器。
typedef struct {S5PC11X_REG32 TCFG0;S5PC11X_REG32 TCFG1;S5PC11X_REG32 TCON;S5PC11X_TIMER ch[4];S5PC11X_REG32 TCNTB4;S5PC11X_REG32 TCNTO4;
} /*__attribute__((__packed__))*/ S5PC11X_TIMERS;
(3)S5PC11X_GetBase_TIMERS函数,把时钟寄存器的基地址强制类型转换为(S5PC11X_TIMERS *)类型。
static inline S5PC11X_TIMERS * S5PC11X_GetBase_TIMERS(void)
{return (S5PC11X_TIMERS *)ELFIN_TIMER_BASE;
}
(4) timers->TCFG0=0x0f00,相当于把0x0f00这个值放到 TCFG0对应的寄存器里。寄存器必须设置为连续或者一一对应的,否则会造成赋值的地址错误。
(5)其余代码与裸机课程一致,见博文定时器——S5PV210定时器的理论与操作。
4.4 env_init函数
(1)选取哪个文件中的env_init函数?
在SI中查询env_init函数时,发现有很多以env_开头的文件中都有env_init函数,如下所示。
这是因为uboot支持各种不同的启动介质(比如norflash、nandflash、inand和sd卡),不同启动介质存取环境变量的方法是不一样的,因此有很多个env_开头的.c文件。
实际使用其中哪一个文件,要根据实际开发板使用的存储介质来判定。这些env_xxx.c文件同时只有一个会起作用,可以通过x210_sd.h中配置的宏来决定哪个文件被包含的。
(2)函数内容与分析
对于X210开发板,env_init函数位于common/env_movi.c文件中,内容如下:
int env_init(void)
{
#if defined(ENV_IS_EMBEDDED) //这个宏定义了,但为何下面的陈述走的是else路线?ulong total;int crc1_ok = 0, crc2_ok = 0;env_t *tmp_env1, *tmp_env2;total = CFG_ENV_SIZE;tmp_env1 = env_ptr;tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);if (!crc1_ok && !crc2_ok)gd->env_valid = 0;else if(crc1_ok && !crc2_ok)gd->env_valid = 1;else if(!crc1_ok && crc2_ok)gd->env_valid = 2;else {/* both ok - check serial */if(tmp_env1->flags == 255 && tmp_env2->flags == 0)gd->env_valid = 2;else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)gd->env_valid = 1;else if(tmp_env1->flags > tmp_env2->flags)gd->env_valid = 1;else if(tmp_env2->flags > tmp_env1->flags)gd->env_valid = 2;else /* flags are equal - almost impossible */gd->env_valid = 1;}if (gd->env_valid == 1)env_ptr = tmp_env1;else if (gd->env_valid == 2)env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */ //为何走的是这条路线?gd->env_addr = (ulong)&default_environment[0];gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */return (0);
}
其中default_environment这个字符数组定义在common/env_common.c文件中,内容如下:
#if defined(CONFIG_S3C6410) || defined(CONFIG_S3C6430) || defined(CONFIG_S5P6440) || defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
uchar default_environment[CFG_ENV_SIZE] = {
#else
uchar default_environment[] = {
#endif#ifdef CONFIG_BOOTARGS"bootargs=" CONFIG_BOOTARGS "\0"
#endif#ifdef CONFIG_BOOTCOMMAND"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif//省略部分代码"\0"
};//#define CONFIG_BOOTARGS "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"
//#define CONFIG_BOOTCOMMAND "movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000"
该函数对uboot自带的环境变量做了基本的初始化,也就是把default_environment这个字符数组的地址,赋给gd->env_addr,以及将env_valid 赋值为1。
uboot自带的环境变量以代码的形式写死在default_environmen[ ]数组中,当uboot被加载到DDR中运行时,在 DDR 中就有了一份环境变量。但实际上SD卡中的环境变量才是我们需要的(为什么不使用uboot自带的环境变量),因为目前还没有将SD卡中的环境变量重定位到DDR中,因此不能使用当前 DDR中的环境变量。后续通过调用 env_relocate 函数(见下文),将SD卡中的环境变量重定位到 DDR中,取代DDR中uboot自带的那一份环境变量之后,才可以使用DDR中的环境变量。
换言之,这个函数没啥用?
4.5 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);
}
(1)该函数初始化波特率,即从baudrate这个环境变量中获取波特率,赋值给gd->bd->bi_baudrate、gd->baudrate。从env_init函数可知(还没有设置波特率,所以三目运算符选择后者?),波特率就是在x210_sd.h文件中配置的宏CONFIG_BAUDRATE,其值为115200。
(2)simple_strtoul函数的作用,是把一个字符串转换为整数。
(3)这里追踪一下getenv_r这个函数。
//读取环境变量name到缓存buf中,读取成功返回n大于0,失败返回-1
int getenv_r (char *name, char *buf, unsigned len)
{int i, nxt;for (i=0; env_get_char(i) != '\0'; i=nxt+1) {int val, n;for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {if (nxt >= CFG_ENV_SIZE) {return (-1);}}if ((val=envmatch((uchar *)name, i)) < 0)continue;/* found; copy out */n = 0;while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0');if (len == n)*buf = '\0';return (n);}return (-1);
}
//判断环境变量是从内存还是从sd卡中赋值的,然后返回index对应的环境变量中的字符
uchar env_get_char (int index)
{uchar c;/* if relocated to RAM */if (gd->flags & GD_FLG_RELOC)c = env_get_char_memory(index);elsec = env_get_char_init(index);return (c);
}
//从内存中读取环境变量字符,作为返回值返回。
uchar env_get_char_memory (int index)
{if (gd->env_valid) {return ( *((uchar *)(gd->env_addr + index)) );} else {return ( default_environment[index] );}
}
/****************************************** Match a name / name=value pair** s1 is either a simple 'name', or a 'name=value' pair.* i2 is the environment index for a 'name2=value2' pair.* If the names match, return the index for the value2, else NULL.*///判断*s1,是否和i2对应的字符串相等,如果相等返回i2。
int envmatch (uchar *s1, int i2)
{while (*s1 == env_get_char(i2++))if (*s1++ == '=')return(i2);if (*s1 == '\0' && env_get_char(i2-1) == '=')return(i2);return(-1);
}
4.6 serial_init函数
uboot有很多个serial_init函数,X210开发板对应的是cpu/s5pc11x/serial.c中的serial_init函数。
/** Initialise the serial port with the given baudrate. The settings* are always 8 data bits, no parity, 1 stop bit, no start bits.**/
int serial_init(void)
{serial_setbrg();return (0);
}
void serial_setbrg(void)
{DECLARE_GLOBAL_DATA_PTR;int i;for (i = 0; i < 100; i++);
}
由代码可知该函数没有什么实质操作,因为在start.S文件中已经对串口进行初始化。
4.7 console_init_f函数
该函数位于common/console.c文件中,内容如下:
/* Called before relocation - use serial functions */
int console_init_f (void)
{gd->have_console = 1;#ifdef CONFIG_SILENT_CONSOLE //没有定义这个宏if (getenv("silent") != NULL)gd->flags |= GD_FLG_SILENT;
#endifreturn (0);
}
console_init_f 函数主要完成控制台第一阶段的初始化,这里的_f表示第一阶段。
这个函数只是将gd->have_console设置为1而已。
4.8 display_banner函数
该函数位于lib_arm/board.c文件中,内容如下:
static int display_banner (void)
{printf ("\n\n%s\n\n", version_string);debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",_armboot_start, _bss_start, _bss_end);#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ //定义了这个宏debug("\t\bMalloc and Stack is above the U-Boot Code.\n");
#elsedebug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif#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);
#endifopen_backlight();//lqm.//open_gprs();return (0);
}
由上面的代码可知,该函数的作用是:串口输出显示uboot的logo、打开背光。
(1)字符数组version_string的内容如下。在uboot源代码中找不到U_BOOT_VERSION这个宏,因为它是在编译所生成的include/version_autogenerated.h文件中定义的。
const char version_string[] =U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;#define CONFIG_IDENT_STRING " for x210"
(2)该函数使用printf函数向串口输出字符数组version_string。
我们知道,printf函数用于将格式化后的字符串输出到标准输出(即屏幕或者说控制台)。根据上面的分析,console_init_f并没有完成控制台的初始化,那为什么可以使用printf函数呢?
追踪printf的实现,其调用puts函数,而puts函数中会判断控制台有没有被初始化好。如果控制台已经初始化好,则调用 fputs 函数完成串口输出(这条线才是控制台);如果还没有初始化好,则会调用 serial_puts 函数,serial_puts 函数再调用 serial_putc 函数,而serial_putc 函数直接操作串口的寄存器来进行内容发送。
void puts (const char *s)
{
#ifdef CONFIG_SILENT_CONSOLE //没有定义这个宏if (gd->flags & GD_FLG_SILENT)return;
#endifif (gd->flags & GD_FLG_DEVINIT) {/* Send to the standard output */fputs (stdout, s);} else {/* Send directly to the handler */serial_puts (s);}
}
什么是控制台?控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送与接收等等),这些通信函数最终会映射到硬件的通信函数中来实现。
uboot中,控制台的通信函数直接映射(中间没有缓冲机制?)到硬件串口的通信函数中,即uboot中是否使用控制台其实并没有本质差别。换句话说,在uboot中控制台是通过串口输出的,非控制台也通过串口输出的。
但在别的体系中,控制台的通信函数映射到硬件通信函数时,可以用软件来做一些中间优化,比如缓冲机制。操作系统中的控制台都使用了缓冲机制,所以有时候 printf 了内容,但是屏幕上并没有显示输出信息,这是因为数据被缓冲了。此时输出信息在控制台的缓冲区中,还没有被刷新到硬件输出设备上。这点尤其体现在输出设备是LCD屏幕时。
(3)打开背光灯的函数内容如下:
static void open_backlight(void)
{unsigned int reg;//open backlight. GPF3_5=1reg = readl(GPF3CON);reg = reg & ~(0xf<<20) | (0x1<<20);writel(reg,GPF3CON);reg = readl(GPF3PUD);reg = reg & ~(0x3<<10) | (0x2<<10);writel(reg,GPF3PUD);reg = readl(GPF3DAT);reg |= (0x1<<5);writel(reg,GPF3DAT);
}
4.9 print_cpuinfo函数
该函数位于cpu/s5pc11x/s5pc110/speed.c文件中,删除条件编译后内容如下:
int print_cpuinfo(void)
{uint set_speed;uint tmp;uchar result_set;#if defined(CONFIG_CLK_533_133_100_100)set_speed = 53300;
#elif defined(CONFIG_CLK_1000_200_166_133)set_speed = 100000;
#endiftmp = (set_speed / (get_ARMCLK()/1000000));if((tmp < 105) && (tmp > 95)){result_set = 1;} else {result_set = 0;}#ifdef CONFIG_MCP_SINGLEprintf("\nCPU: S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#endifprintf(" APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1printf(" MPLL = %ldMHz, EPLL = %ldMHz\n",get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);printf(" HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",get_HCLKD()/1000000, get_PCLKD()/1000000);printf(" HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",get_HCLKP()/1000000, get_PCLKP()/1000000);printf(" SCLKA2M = %ldMHz\n", get_SCLKA2M()/1000000);
#endifputs("Serial = CLKUART ");return 0;
}
顾名思义,该函数实现打印cpu的一些信息的功能,如下所示:
OKU-Boot 1.3.4 (Mar 7 2016 - 11:55:20) for x210//***************显示这部分内容
CPU: S5PV210@1000MHz(OK)APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHzMPLL = 667MHz, EPLL = 96MHzHclkDsys = 166MHz, PclkDsys = 83MHzHclkPsys = 133MHz, PclkPsys = 66MHzSCLKA2M = 200MHz
Serial = CLKUART
//***************显示这部分内容
Board: X210
DRAM: 512 MB
Flash: 8 MB
SD/MMC: 3728MB
In: serial
Out: serial
Err: serial
[LEFT UP] boot mode
checking mode for fastboot ...
Hit any key to stop autoboot: 0
x210 #
(1)get_ARMCLK函数
该函数用于获取(时钟域24MHz经过APLL倍频,再经过分频器以后所获得的)CPU的频率。
/* return ARMCORE frequency */
ulong get_ARMCLK(void)
{ulong div,apll_ratio;div = CLK_DIV0_REG;apll_ratio = ((div>>0) & 0x7);return ((get_PLLCLK(APLL)) / (apll_ratio + 1));}
(2)get_PLLCLK函数
该函数用于获取PLL倍频以后的时钟频率:APLL、MPLL、 EPLL。
/* ------------------------------------------------------------------------- */
/* NOTE: This describes the proper use of this file.** CONFIG_SYS_CLK_FREQ should be defined as the input frequency of the PLL.** get_FCLK(), get_HCLK(), get_PCLK() and get_UCLK() return the clock of* the specified bus in HZ.*/
/* ------------------------------------------------------------------------- */static ulong get_PLLCLK(int pllreg)
{ulong r, m, p, s;if (pllreg == APLL) {r = APLL_CON0_REG;m = (r>>16) & 0x3ff;} else if (pllreg == MPLL) {r = MPLL_CON_REG;m = (r>>16) & 0x3ff;} else if (pllreg == EPLL) {r = EPLL_CON_REG;m = (r>>16) & 0x1ff;} elsehang();p = (r>>8) & 0x3f;s = r & 0x7;if (pllreg == APLL) s= s-1;return (m * (CONFIG_SYS_CLK_FREQ / (p * (1 << s))));
}
4.10 checkboard函数
此函数位于board/samsung/x210/x210.c文件中,该函数只是在控制台显示“Board: X210”而已。
#ifdef CONFIG_DISPLAY_BOARDINFO
int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)printf("\nBoard: VOGUESV210\n");
#elseprintf("\nBoard: X210\n");
#endif //CONFIG_VOGUES
#elseprintf("\nBoard: X210\n");
#endifreturn (0);
}
#endif
4.11 init_func_i2c函数
该函数位于lib_arm/board.c文件中,由于条件编译,此函数实际上没有执行。
X210开发板的uboot中并没有使用I2C,将来如果要扩展I2C来外接硬件,则在x210_sd.h文件中配置相应的宏即可开启。
4.12 dram_init函数
此函数位于lib_arm/board.c文件中,内容如下:
int dram_init(void)
{DECLARE_GLOBAL_DATA_PTR;gd->bd->bi_dram[0].start = PHYS_SDRAM_1;gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;#if defined(PHYS_SDRAM_2)gd->bd->bi_dram[1].start = PHYS_SDRAM_2;gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif#if defined(PHYS_SDRAM_3)gd->bd->bi_dram[2].start = PHYS_SDRAM_3;gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endifreturn 0;
}
真正的DDR初始化函数已经在汇编阶段中执行,此处只是把chip1与chip2的首地址和大小赋值给全局变量gd->bd。
由代码可知,我们只要定义了相应的宏PHYS_SDRAM_3,就可以扩展chip3。
4.13 display_dram_config函数
此函数位于lib_arm/board.c文件中,内容如下:
/** 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)
{int i;#ifdef DEBUGputs ("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");}
#elseulong size = 0;for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {size += gd->bd->bi_dram[i].size;}puts("DRAM: ");print_size(size, "\n");
#endifreturn (0);
}
(1)此函数作用是计算chip1、chip2一共多少内存并输出,即启动信息中的“DRAM: 512 MB”。
//……
CPU: S5PV210@1000MHz(OK)APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHzMPLL = 667MHz, EPLL = 96MHzHclkDsys = 166MHz, PclkDsys = 83MHzHclkPsys = 133MHz, PclkPsys = 66MHzSCLKA2M = 200MHz
Serial = CLKUART
Board: X210
DRAM: 512 MB //显示的内容
Flash: 8 MB
SD/MMC: 3728MB
//……
(2)uboot中的“bdinfo”命令,可以打印出gd->bd中记录的所有与硬件相关的全局变量的值,因此可以得知DDR的配置信息。
x210 # bdinfo
arch_number = 0x00000998
env_t = 0x00000000
boot_params = 0x30000100
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000
ethaddr = 00:40:5C:26:0A:5B
ip_addr = 192.168.1.88
baudrate = 115200 bps
x210 #
5、mem_malloc_init函数
/* armboot_start is defined in the board-specific linker script */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#elsemem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif
mem_malloc_init函数位于lib_arm/board.c文件中,内容如下:
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);
}
该函数用来初始化uboot的堆管理器,它只是设置了堆的开始与终止地址,以及一个malloc_brk。
(1)uboot中维护了一段堆内存,因此在uoot中也可以使用malloc、free来申请与释放内存。
(2)我们在DDR内存中给uboot堆预留了896KB的内存(为何不是912KB)。
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
- CFG_UBOOT_BASE=0x33e0_0000;
- CFG_UBOOT_SIZE=2MB=2*1024*1024=0x0020_0000;
- CFG_MALLOC_LEN=CFG_ENV_SIZE + 896*1024=0x4000(16KB)+896KB=912KB;
- CFG_STACK_SIZE=512*1024=512KB。
6、mmc_initialize函数
三星用一套uboot同时满足很多系列型号的开发板,用#if条件编译配合CONFIG_xxx宏来选定特定的开发板,然后进行独有的一些初始化。这里进入CONFIG_X210宏这个分支:
#if defined(CONFIG_X210)#if defined(CONFIG_GENERIC_MMC)puts ("SD/MMC: ");mmc_exist = mmc_initialize(gd->bd);if (mmc_exist != 0)puts ("0 MB\n");#endif#if defined(CONFIG_CMD_NAND)puts("NAND: ");nand_init();#endif#endif /* CONFIG_X210 */
其中mmc_initialize函数位于drivers/mmc/mmc.c文件中,内容如下:
int mmc_initialize(bd_t *bis)
{struct mmc *mmc;int err;INIT_LIST_HEAD(&mmc_devices);cur_dev_num = 0;if (board_mmc_init(bis) < 0)//注意这里cpu_mmc_init(bis);#if defined(DEBUG_S3C_HSMMC)print_mmc_devices(',');
#endif#ifdef CONFIG_CHECK_X210CV3mmc = find_mmc_device(1);//lqm
#elsemmc = find_mmc_device(0);
#endifif (mmc) {err = mmc_init(mmc);if (err)err = mmc_init(mmc);if (err) {printf("Card init fail!\n");return err;}}printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));return 0;
}
(1)mmc_initialize函数,主要用来初始化 SoC 内部的 SD/MMC 控制器。
(2)mmc_initialize是一个(与具体硬件架构无关的)MMC初始化函数,所有使用了这套架构的代码都可以调用此函数来完成MMC的初始化。
mmc_initialize函数再调用board_mmc_init、cpu_mmc_init函数,来完成具体硬件的MMC控制器的初始化工作。其中cpu_mmc_init函数位于cpu/s5pc11x/cpu.c文件中,该函数又间接调用drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化具体硬件的MMC控制器。
这说明uboot中对硬件(比如网卡、SD卡)的操作,其实都是通过借用Linux内核中的驱动来实现的。uboot源码顶层目录下有一个drivers文件夹,里面存放着从Linux内核中移植过来的各种驱动源文件。
(3)本节内容有待深入研究。
7、env_relocate函数
该函数位于common/env_common.c文件中,内容如下:
void env_relocate (void)
{DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,gd->reloc_off);#ifdef CONFIG_AMIGAONEG3SEenable_nvram();
#endif#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);DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endifif (gd->env_valid == 0) {
#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */puts ("Using default environment\n\n");
#elseputs ("*** Warning - bad CRC, using default environment\n\n");show_boot_progress (-60);
#endifset_default_env();}else {env_relocate_spec ();}gd->env_addr = (ulong)&(env_ptr->data);#ifdef CONFIG_AMIGAONEG3SEdisable_nvram();
#endif
}
该函数主要完成环境变量的重定位,即完成将环境变量从SD卡读取到DDR中的任务。
(1)从哪里获取环境变量?
SD卡中有一些独立的扇区(8个扇区)用来存储环境变量。
我们在烧录系统时(参见博文如何将镜像烧写至iNand),烧录了uboot分区、kernel分区和rootfs分区,没有烧录env分区(环境分区),因此当我们烧录完系统第一次启动时env分区是空的。
这时如果去SD卡的env分区读取环境变量,则肯定会失败(表现为进行CRC校验时失败)。
此时uboot会选择它自带的那一份环境变量(见上面“4.4 env_init函数”中的描述),即默认的环境变量。当uboot被加载到DDR中运行时,这份环境变量就会被加载到 DDR 中。
当你使用saveenv命令时,这份环境变量就会被写入SD卡的env分区(也可能是uboot设计了第一次读取默认环境变量后就写如SD卡的env分区的机制),下次启动uboot时,就会从SD卡的env分区读取环境变量到DDR中,此时就不会失败了。
(2)如何将环境变量从SD卡读取到DDR中?
通过env_relocate_spec函数内部的movi_read_env函数完成的。
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)uint *magic = (uint*)(PHYS_SDRAM_1);if ((0x24564236 != magic[0]) || (0x20764316 != magic[1]))movi_read_env(virt_to_phys((ulong)env_ptr));if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
8、IP地址、MAC地址的确定
/* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");/* 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;}}
(1)gd->bd这个变量保存着开发板的IP地址,这个IP地址来源于环境变量ipaddr。
getenv_IPaddr函数内容如下,其内部调用getenv函数用来获取字符串格式的IP地址,然后调用函数string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。
IPaddr_t getenv_IPaddr (char *var)
{return (string_to_ip(getenv(var)));
}
9、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 */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_LCDdrv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARDdrv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFERdrv_logbuff_init ();
#endifdrv_system_init ();
#ifdef CONFIG_SERIAL_MULTIserial_devices_init ();
#endif
#ifdef CONFIG_USB_TTYdrv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLEdrv_nc_init ();
#endifreturn (0);
}
我们知道,Linux内核中的驱动都有相应的设备初始化函数。而Linux内核在启动过程中,有一个名为devices_init的函数,其作用就是集中执行各种硬件驱动的初始化t函数。
这里的devices_init函数,其实就是从Linux内核中移植过来的(uboot中很多设备的驱动都是直接移植Linux内核的),它的作用也是去执行所有从Linux内核中继承来的那些硬件驱动的初始化函数。
9、jumptable_init函数
该函数位于common/exports.c文件中,内容如下:
void jumptable_init (void)
{int i;gd->jt = (void **) malloc (XF_MAX * sizeof (void *));for (i = 0; i < XF_MAX; i++)gd->jt[i] = (void *) dummy;gd->jt[XF_get_version] = (void *) get_version;gd->jt[XF_malloc] = (void *) malloc;gd->jt[XF_free] = (void *) free;gd->jt[XF_getenv] = (void *) getenv;gd->jt[XF_setenv] = (void *) setenv;gd->jt[XF_get_timer] = (void *) get_timer;gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;gd->jt[XF_udelay] = (void *) udelay;gd->jt[XF_simple_strtol] = (void *) simple_strtol;gd->jt[XF_strcmp] = (void *) strcmp;
#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 defined(CONFIG_CMD_I2C)gd->jt[XF_i2c_write] = (void *) i2c_write;gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif
}
jumptable跳转表其实就是一个函数指针数组,这个数组的成员都是函数指针(或者说函数名)。
只要将这些函数指针与具体的函数进行绑定,将来就可以通过这些函数指针来执行具体的函数。这其实就是利用C语言来实现面向对象编程,在Linux内核中有很多这种技巧。
通过分析发现,跳转表只是被赋值而已,从未被引用,因此跳转表在uboot中根本就没有使用。
10、console_init_r函数
该函数位于common/console.c文件中,内容如下:
int console_init_r (void)
{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 havea bmp to display */if (getenv("splashimage") != NULL)gd->flags |= GD_FLG_SILENT;
#endif/* Scan devices looking for input and output devices */for (i = 1;(i <= items) && ((inputdev == NULL) || (outputdev == NULL));i++) {device_t *dev = ListGetPtrToItem (devlist, i);if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {inputdev = dev;}if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {outputdev = dev;}}/* Initializes output console first */if (outputdev != NULL) {console_setfile (stdout, outputdev);console_setfile (stderr, outputdev);}/* Initializes input console */if (inputdev != NULL) {console_setfile (stdin, inputdev);}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 */#ifndef CONFIG_X210/* Setting environment variables */for (i = 0; i < 3; i++) {setenv (stdio_names[i], stdio_devices[i]->name);}
#endif#if 0/* If nothing usable installed, use only the initial console */if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL))return (0);
#endifreturn (0);
}
该函数完成控制台纯软件架构方面的初始化,也就是给控制台相关的数据结构填充相应的值,因此属于纯软件配置类型的初始化。
由“4.8 display_banner函数”可知,uboot控制台直接调用串口通信的函数,因此uboot是否使用控制台并没有什么分别。
11、enable_interrupts函数
该函数位于lib_arm/interrupts.c文件中,内容如下:
#ifdef CONFIG_USE_IRQ
/* enable IRQ interrupts */
void enable_interrupts (void)
{unsigned long temp;__asm__ __volatile__("mrs %0, cpsr\n""bic %0, %0, #0x80\n""msr cpsr_c, %0": "=r" (temp):: "memory");
}
//省略部分代码#endif
(1)此函数对CPSR寄存器的中断标志位进行设置,见博客ARM的37个通用寄存器。
(2)因为uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此此函数无用。
12、loadaddr、bootfile两个环境变量
/* 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
loadaddr、bootfile这两个环境变量都与内核启动有关,内核启动时会参考这两个环境变量的值。
13、board_late_init函数
顾名思义,前面该初始化的都已经初始化,剩下的一些初始化都在此函数中,也侧面说明开发板级别的硬件软件初始化告一段落。
此函数位于board/samsung/x210/x210.c文件中。
对于X210开发板来说,因为条件编译的缘故,这个函数是空的。
14、eth_initialize函数
此函数是网卡相关的初始化,是网卡芯片本身的一些初始化(而不是SoC与网卡芯片连接时SoC这边的初始化)。对于X210开发板,这个函数是空的,因为X210开发板的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。
15、x210_preboot_init函数(LCD和logo的显示)
/****************lxg added**************/
#ifdef CONFIG_MPADextern int x210_preboot_init(void);x210_preboot_init();
#endif
该函数位于board/samsung/x210/x210.c文件中,内容如下:
int x210_preboot_init(void)
{mpadfb_init();return 1;
}
//位于drivers/video/mpadfb.c文件中void mpadfb_init()
{
// unsigned short int *pFB;
// int i;fb_init();//lqm masked for testlcd_port_init();lcd_reg_init();
#ifdef CONFIG_CHECK_X210CV3init_logo();
#endifdisplay_logo(&s5pv210_fb);
#if(DISP_MODE == TRULY043)backlight_brigness_init(0);
#else//AT070TN92backlight_brigness_init(1);
#endif// pFB = (unsigned short int *)CFG_LCD_FBUFFER;/*for(i=0; i<800*480; i++)*pFB++ = 0xf800;*/
/* for(i=0; i<800*100; i++)*pFB++ = 0xf800;for(i=800*100; i<800*200; i++)*pFB++ = 0x07e0;for(i=800*200; i<800*480; i++)*pFB++ = 0x001f;
*/#if(DISP_MODE == TRULY043)writel((readl(GPF3DAT) & ~(0x1<<5)) | (0x1<<5), GPF3DAT);
#endif
}
该函数主要完成X210开发板在启动起来之前的一些初始化,以及LCD屏幕上的logo显示。
16、check_menukey_to_update_from_sd函数
if(check_menu_update_from_sd()==0)//update mode{puts ("[LEFT DOWN] update mode\n");run_command("fdisk -c 0",0);update_all();}elseputs ("[LEFT UP] boot mode\n");
在uboot启动的最后阶段设计了一个自动更新的功能。
我们可以将要升级的镜像放到SD卡中,然后开机时在uboot启动的最后阶段检查升级标志,即是否按下"LEFT"这个按键。如果按下此键,则表示update mode,uboot会自动从SD卡中读取镜像文件然后烧录到iNand中。如果没有按下,则表示boot mode,uboot不hi执行update,而是正常启动。
这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。
17、main_loop函数
/* main_loop() can return to retry autoboot, if so just run it again. */for (;;) {main_loop ();}
该函数位于common/main.c文件中,关于此函数的讲解,见博客uboot源码——内核启动分析。