uboot启动流程——C阶段的start_armboot函数

news/2024/11/29 18:48:46/

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、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源码——内核启动分析。


http://www.ppmy.cn/news/334836.html

相关文章

uboot流程分析--修改android启动模式按键【转】

本文转载自&#xff1a;http://blog.csdn.net/dkleikesa/article/details/9792747 本人用的android平台用的bootloader用的是uboot&#xff0c;貌似大多数手持设备平台都不用这个&#xff0c;因为功能过于强大用不上&#xff0c;反而显得太复杂了。不知道这个平台开发者是怎么想…

Linux中断子系统(三)之GIC中断处理过程

Linux中断子系统&#xff08;三&#xff09;之GIC中断处理过程 备注&#xff1a;   1. Kernel版本&#xff1a;5.4   2. 使用工具&#xff1a;Source Insight 4.0   3. 参考博客&#xff1a; Linux中断子系统&#xff08;一&#xff09;中断控制器及驱动分析 Linux中断子…

Linux内核深入理解系统调用(1):初始化-入口-处理-退出

Linux内核深入理解系统调用&#xff08;1&#xff09;&#xff1a;初始化-入口-处理-退出 rtoax 2021年3月 1. Linux 内核系统调用简介 这次提交为 linux内核解密 添加一个新的章节&#xff0c;从标题就可以知道, 这一章节将介绍Linux 内核中 System Call 的概念。章节内容的选…

「内核知识」Linux下的系统调用write

本文以x86_64平台为例&#xff0c;分析linux下的系统调用是如何被执行的。 假设目标系统调用是&#xff0c;其对应的内核源码为&#xff1a; // fs/read_write.c SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count) {return ksys_write(fd, …

u-boot-1.1.6源码分析

P { margin-bottom: 0.21cm; }A:link { }CODE.ctl { font-family: "Lohit Hindi",monospace; } u-boot-1.1.6源码分析 15年11月1日15:22:59 想要分析一个大的程序是从哪一个文件开始执行的&#xff0c;首先是分析它的Makefile&#xff0c;当然也可以采取一个取巧的…

Interventional Contrastive Learning with Meta Semantic Regularizer

1. 摘要 基于对比学习(CL)的自我监督学习模型以成对的方式学习视觉表征。虽然目前流行的CL模型已经取得了很大的进展&#xff0c;但在本文中&#xff0c;我们发现了一个一直被忽视的现象&#xff1a;当用完整图像训练CL模型时&#xff0c;在完整图像中测试的性能比在前景区域测…

opencv图像算法

图像的对比度增强 一&#xff1a; 绘制直方图 就是把各个像素值所含有的个数统计出来&#xff0c;然后画图表示。 可以看到在当前图像中&#xff0c;哪个像素值的个数最多。 同时&#xff0c;可以看当前图像总体的像素值大小在哪些范围。。靠近0的话&#xff0c;说明图像偏暗…

Linux Interrupt

在面试的时候我们常常问或者被问一个问题&#xff1a;几种中断下半部机制softirq、tasklet、workqueue有什么区别&#xff1f;linux为什么要设计这几种机制&#xff1f;真正能够回答清楚的人还是少数的。下面我们就详细分析一下这其中的区别。 本文的代码分析基于linux kernel …