1.start_armboot函数简介
1.1一个很长的函数
1)这个函数在uboot/lib_arm/board.c的第444行开始到908行结束。
2)450行还不是全部,因为里面还调用了别的函数。
3)为什么这么长的函数,怎么不分成两三个函数?主要因为这个函数整个构成了uboot启动的第二阶段。
1.2一个函数组成uboot第二阶段
1.3宏观分析:uboot第二阶段应该做什么
1)概括来讲uboot第一阶段主要就是初始化了SoC内部的一些部件(譬如看门狗、时钟),然后初始化DDR并且完成重定位。
2)由宏观分析来讲,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片••••)、uboot本身的一些东西(uboot的命令、环境变量等••••)。然后最终初始化完必要的东西后进入uboot的命令行准备接受命令。
1.4思考:uboot第二阶段完结于何处?
1)uboot启动后自动运行打印出很多信息(这些信息就是uboot在第一和第二阶段不断进行初始化时,打印出来的信息)。然后uboot进入了倒数bootdelay秒然后执行bootcmd对应的启动命令。
2)如果用户没有干涉则会执行bootcmd进入自动启动内核流程(uboot就死掉了);此时用户可以按下回车键打断uboot的自动启动进入uboot的命令行下。然后uboot就一直工作在命令行下。
3)uboot的命令行就是一个死循环,循环体内不断重复:接收命令、解析命令、执行命令。这就是uboot最终的归宿。
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
typedef int (init_fnc_t) (void);定义一个函数类型,函数的参数列表为空,返回值为int类型。
init_fnc_ptr是一个二重函数指针,回顾高级C语言中讲过:二重指针的作用有2个(其中一个是用来指向一重指针),一个是用来指向指针数组。因此这里的init_fuc_ptr可以用来指向一个函数指针数组。我们可以进一步分析,这是一个数组,数组里面都是函数,我们知道函数名就是函数指针,因此整个数组的元素就是若干个int (init_fnc_t) (void)。
char *s;
int mmc_exist = 0;
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
#if defined(CONFIG_BOOT_MOVINAND)
uint *magic = (uint *) (PHYS_SDRAM_1);
#endif
/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
我们在x210_sd.h中定义了。
ulong gd_base;
定义一个全局变量。
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#define CFG_UBOOT_BASE 0xc3e00000
#define CFG_UBOOT_SIZE (2*1024*1024)
#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 896*1024)
#define CFG_ENV_SIZE 0x4000 /* Total Size of Environment Sector */
#define CFG_STACK_SIZE 512*1024
内存排布
1)uboot区 CFG_UBOOT_BASE-xx(长度为uboot的实际长度)
2)堆区 长度为CFG_MALLOC_LEN,实际为912KB
3)栈区 长度为CFG_STACK_SIZE,实际为512KB
4)gd 长度为sizeof(gd_t),实际36字节
5)bd 长度为sizeof(bd_t),实际为44字节左右
6)内存间隔 为了防止高版本的gcc的优化造成错误。
-------------------------------------------------gd_t的代码讲解开始-------------------------------------------------------------------------------
gd_t的研究:
typedef struct global_data {
bd_t *bd; 指针类型,4字节。//struct board_info指针,保存板子信息
------------------------------------------------------------bd_t代码讲解开始---------------------
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */ //串口波特率,4字节
unsigned long bi_ip_addr; /* IP Address *///IP地址,4字节
unsigned char bi_enetaddr[6]; /* Ethernet adress */,MAC地址,6字节
struct environment_s *bi_env;环境变量,4字节
ulong bi_arch_number; /* unique id for this board *///板子ID号,4字节
ulong bi_boot_params; /* where this board expects params *///启动参数,4字节
struct /* RAM configuration *///DRAM BANKS配置,起始地址与长度,16个字节
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
------------------------------------------------------------bd_t代码讲解结束---------------------
unsigned long flags; //指示标志,如设备已经初始化标志等,4字节
unsigned long baudrate; //串口波特率,4字节
unsigned long have_console; /* serial_init() was called *///串口初始化标志,4字节
unsigned long reloc_off; /* Relocation Offset */重定位偏移。4字节
unsigned long env_addr; /* Address of Environment struct */环境参数地址,4字节
unsigned long env_valid; /* Checksum of Environment valid? *///环境参数CRC检验有效标志,4字节
unsigned long fb_base; /* base address of frame buffer */frame buffer的基址,4字节
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
phys_size_t ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */,跳转向量表
} gd_t;
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
1)#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
定义了一个全局变量名字叫gd,这个全局变量是一个结构体指针类型,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中,后面的asm("r8")是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。
2)综合分析,DECLARE_GLOBAL_DATA_PTR就是定义了一个要放在寄存器r8中的全局变量,名字叫gd,类型是一个指向gd_t类型变量的指针。
3)为什么要定义为register?因为这个全局变量gd(global data的简称)是uboot中很重要的一个全局变量(准确的说这个全局变量是一个结构体,里面有很多内容,这些内容加起来构成的结构体就是uboot中常用的所有的全局变量),这个gd在程序中经常被访问,因此放在register中提升效率。因此纯粹是运行效率方面考虑,和功能要求无关。并不是必须的。
4)gd_t定义在include/asm-arm/global_data.h中。
gd_t中定义了很多全局变量,都是整个uboot使用的;其中有一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布。
-------------------------------------------------gd_t的代码讲解结束-------------------------------------------------------------------------------
#ifdef CONFIG_USE_IRQ
gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
gd = (gd_t*)gd_base; //将得到的gd_base强制类型转换成gd_t结构体类型的指针,并赋值给gd。
Gd类型是一个指向gd_t类型变量的指针
#else
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#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));
将gd指针转化成字符型,bd_t是结构体类型。
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
//_armboot_start 在start.S中定义为_start,而_start为代码的起始地址
//只包含 RO(TEXT) 和 RW(DATA) 段.重定位前的值为0x0,此时指向flash,
//重定位后则指向RAM中的某一地址
//由此可以知道: _bss_start - _armboot_start 的值即是在第一阶段从
//flash中重定位到RAM中的那部分代码的长度,也即TEXT和DATA数据段,
//这个值与start.S中的重定位那部分代码所计算的值是相等的
//所以,monitor_flash_len表示从flash中搬来的代码的长度
总的来说,第一种说法中提到的在汇编代码中对符号赤裸裸的引用,意思是直接将符号的绝对地址拿出来去取值,也就是反汇编后括号[ ]里面存放的直接地址。也就是说,_armboot_start是一个地址0xc3e00058,其中的内容是0xc3e00010,汇编对 _armboot_start的引用是直接将其替换为其表示的地址0xc3e00058,而非其中的内容0xc3e00010。这就是"赤裸裸"的引用。
在c代码中如果应用第一阶段汇编代码中定义的符号,变量或常量时实际上是引用的该符号处存放的内容,而不是该符号的地址。
引用的是0xc3e00010,而不是0xc3e00058。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
}
}
1)init_sequence是一个函数指针数组,数组中存储了很多个函数指针,这些指向指向的函数都是init_fnc_t类型(特征是接收参数是void类型,返回值是int)。
2)init_sequence在定义时就同时给了初始化,初始化的函数指针都是一些函数名。(C语言高级专题中讲过:函数名的实质)
3)init_fnc_ptr是一个二重函数指针,可以指向init_sequence这个函数指针数组。
4)用for循环肯定是想要去遍历这个函数指针数组(遍历的目的也是去依次执行这个函数指针数组中的所有函数)。思考:如何遍历一个函数指针数组?有2种方法:第一种也是最常用的一种,用下标去遍历,用数组元素个数来截至。第二种不常用,但是也可以。就是在数组的有效元素末尾放一个标志,依次遍历到标准处即可截至(有点类似字符串的思路)。
我们这里使用了第二种思路。因为数组中存的全是函数指针,因此我们选用了NULL来作为标志。我们遍历时从开头依次进行,直到看到NULL标志截至。这种方法的优势是不用事先统计数组有多少个元素。
5)init_fnc_t的这些函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1.所以我们在遍历时去检查函数返回值,如果遍历中有一个函数返回值不等于0则hang()挂起。从分析hang函数可知:uboot启动过程中初始化板级硬件时不能出任何错误,只要有一个错误整个启动就终止,除了重启开发板没有任何办法。
6)init_sequence中的这些函数,都是board级别的各种硬件初始化。下面来讲解 下init_sequence函数指针数组中的各种函数:
1.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内部的初始化,看到函数是计算FIQ与IRQ的栈起始地址。我们在uboot没有使用中断,所以相当于空函数,直接返回0。
2.int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();
1)board_init在uboot/board/samsung/x210/x210.c中,这个看名字就知道是x210开发板相关的初始化。
2)DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便。可以看出把gd的声明定义成一个宏的原因就是我们要到处去使用gd,因此就要到处声明,定义成宏比较方便。
3)网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210_sd.h中定义的,这个宏用来配置开发板的网卡的。dm9000_pre_init函数就是对应的DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。
4)这个函数中主要是网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的正确的,移植的时候驱动是不需要改动的,关键是这里的基本初始化。因为这些基本初始化是硬件相关的。
-----------------------DM9000的初始化函数讲解开始----------------------------------------
static void dm9000_pre_init(void)
{
unsigned int tmp;
#if defined(DM9000_16BIT_DATA)
我们在x210_sd.h中定义了。
SROM_BW_REG &= ~(0xf << 4); //第一步:将4~7位清零;
#define SROM_BW_REG __REG(ELFIN_SROM_BASE+0x0)
#define ELFIN_SROM_BASE 0xE8000000
SROM_BW_REG |= (1<<7) | (1<<6) | (1<<5) | (1<<4);//第二步:将4~7位进行设置;
#else
SROM_BW_REG &= ~(0xf << 4);
SROM_BW_REG |= (0<<6) | (0<<5) | (0<<4);
#endif
SROM_BC1_REG = ((0<<28)|(1<<24)|(5<<16)|(1<<12)|(4<<8)|(6<<4)|(0<<0));//uboot配置各种时钟
#define SROM_BC1_REG __REG(ELFIN_SROM_BASE+0x8)
#define ELFIN_SROM_BASE 0xE8000000
//SROM_BC1_REG = ((0<<28)|(0<<24)|(5<<16)|(0<<12)|(0<<8)|(0<<4)|(0<<0));//kernel
tmp = MP01CON_REG;//第一步:读
#define MP01CON_REG __REG(ELFIN_GPIO_BASE + MP01CON_OFFSET)
#define ELFIN_GPIO_BASE 0xE0200000
#define MP01CON_OFFSET 0x2E0
tmp &=~(0xf<<4);//第二步:改
tmp |=(2<<4);
MP01CON_REG = tmp;//第三步:写
}
总结:这一段DM9000的配置代码做了什么:
- 配置各种握手时序。
- 配置CS管脚,并初始化响应的GPIO。
-----------------------DM9000的初始化函数讲解结束----------------------------------------
#endif
gd->bd->bi_arch_number = MACH_TYPE;//设置板子的机器码ID:2456
#define MACH_TYPE 2456
#define UBOOT_MAGIC (0x43090000 | MACH_TYPE)
1)bi_arch_number是board_info中的一个元素,含义是:开发板的机器码。所谓机器码就是uboot给这个开发板定义的一个唯一编号。
2)机器码的主要作用就是在uboot和linux内核之间进行比对和适配。
3)嵌入式设备中每一个设备的硬件都是定制化的,不能通用。嵌入式设备的高度定制化导致硬件和软件不能随便适配使用。这就告诉我们:这个开发板移植的内核镜像绝对不能下载到另一个开发板去,否则也不能启动,就算启动也不能正常工作,有很多隐患。因此linux做了个设置:给每个开发板做个唯一编号(机器码),然后在uboot、linux内核中都有一个软件维护的机器码编号。然后开发板、uboot、linux三者去比对机器码,如果机器码对上了就启动,否则就不启动(因为软件认为我和这个硬件不适配)。
4)MACH_TYPE在x210_sd.h中定义,值是2456,并没有特殊含义,只是当前开发板对应的编号。这个编号就代表了x210这个开发板的机器码,将来这个开发板上面移植的linux内核中的机器码也必须是2456,否则就启动不起来。
5)uboot中配置的这个机器码,会作为uboot给linux内核的传参的一部分传给linux内核,内核启动过程中会比对这个接收到的机器码,和自己本身的机器码相对比,如果相等就启动,如果不想等就不启动。
6)理论上来说,一个开发板的机器码不能自己随便定。理论来说有权利去发放这个机器码的只有uboot官方,所以我们做好一个开发板并且移植了uboot之后,理论上应该提交给uboot官方审核并发放机器码(好像是免费的)。但是国内的开发板基本都没有申请(主要是因为国内开发者英文都不行,和国外开源社区接触比较少),都是自己随便编号的。随便编号的问题就是有可能和别人的编号冲突,但是只要保证uboot和kernel中的编号是一致的,就不影响自己的开发板启动。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);//启动参数即启动的相对地址:0x30000100
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define MEMORY_BASE_ADDRESS 0x30000000
1)bd_info中另一个主要元素,bi_boot_params表示uboot给linux kernel启动时的传参的内存地址。也就是说uboot给linux内核传参的时候是这么传的:uboot事先将准备好的传参(字符串,就是bootargs)放在内存的一个地址处(就是bi_boot_params),然后uboot就启动了内核(uboot在启动内核时真正是通过寄存器r0 r1 r2来直接传递参数的,其中有一个寄存器中就是bi_boot_params)。内核启动后从寄存器中读取bi_boot_params就知道了uboot给我传递的参数到底在内存的哪里。然后自己去内存的那个地方去找bootargs。
2)经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址就被分配用来做内核传参了。所以在uboot的其他地方使用内存时要注意,千万不敢把这里给淹没了。
背景:关于DDR的配置:
1)软件初始化DDR。
2)注意:这里的初始化DDR和汇编阶段lowlevel_init中初始化DDR是不同的。当时是硬件的初始化,目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性配置、地址设置的初始化,是纯软件层面的。
3)软件层次初始化DDR的原因:对于uboot来说,他怎么知道开发板上到底有几片DDR内存,每一片的起始地址、长度这些信息呢?在uboot的设计中采用了一种简单直接有效的方式:程序员在移植uboot到一个开发板时,程序员自己在x210_sd.h中使用宏定义去配置出来板子上DDR内存的信息,然后uboot只要读取这些信息即可。(实际上还有另外一条思路:就是uboot通过代码读取硬件信息来知道DDR配置,但是uboot没有这样。实际上PC的BIOS采用的是这种)
4)x210_sd.h的496行到501行中使用了标准的宏定义来配置DDR相关的参数。主要配置了这么几个信息:有几片DDR内存、每一片DDR的起始地址、长度。这里的配置信息我们在uboot代码中使用到内存时就可以从这里提取使用(想象uboot中使用到内存的地方都不是直接用地址数字的,都是用宏定义的)
return 0;
}
3.int interrupt_init(void)
{
1)看名字函数是和中断初始化有关的,但是实际上不是,实际上这个函数是用来初始化定时器的(实际使用的是Timer4)。
2)裸机中讲过:210共有5个PWM定时器。其中Timer0-timer3都有一个对应的PWM信号输出的引脚。而Timer4没有引脚,无法输出PWM波形。Timer4在设计的时候就不是用来输出PWM波形的(没有引脚,没有TCMPB寄存器),这个定时器被设计用来做计时。
3)Timer4用来做计时时要使用到2个寄存器:TCNTB4、TCNTO4。TCNTB中存了一个数,这个数就是定时次数(每一次时间是由时钟决定的,其实就是由2级时钟分频器决定的)。我们定时时只需要把定时时间/基准时间=数,将这个数放入TCNTB中即可;我们通过TCNTO寄存器即可读取时间有没有减到0,读取到0后就知道定的时间已经到了。
4)使用Timer4来定时,因为没有中断支持,所以CPU不能做其他事情同时定时,CPU只能使用轮询方式来不断查看TCNTO寄存器才能知道定时时间到了没。因为Timer4的定时是不能实现微观上的并行。uboot中定时就是通过Timer4来实现定时的。所以uboot中定时时不能做其他事(考虑下,典型的就是bootdelay,bootdelay中实现定时并且检查用户输入是用轮询方式实现的,原理参考裸机中按键章节中的轮询方式处理按键)
5)interrupt_init函数将timer4设置为定时10ms。关键部位就是get_PCLK函数获取系统设置的PCLK_PSYS时钟频率,然后设置TCFG0和TCFG1进行分频,然后计算出设置为10ms时需要向TCNTB中写入的值,将其写入TCNTB,然后设置为auto reload模式,然后开定时器开始计时就没了。
总结:在学习这个函数时,注意标准代码和之前裸机代码中的区别,重点学会:通过定义结构体的方式来访问寄存器,通过函数来自动计算设置值以设置定时器。
S5PC11X_TIMERS *const timers = S5PC11X_GetBase_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;
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00; //对TCFG0进行设置,预分频设置为15
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 @ 66 MHz,使用的是PCLK_PSYS
*/
timer_load_val = get_PCLK() / (16 * 100); //这句话就是设置10ms的时间,有多少CNT
ulong get_PCLK(void)
{
ulong hclk;
uint div = CLK_DIV0_REG;
# define io_p2v(PhAdd) (PhAdd)
# define __REG(x) (*((volatile u32 *)io_p2v(x)))
#define CLK_DIV0_REG __REG(ELFIN_CLOCK_POWER_BASE+CLK_DIV0_OFFSET)
#define ELFIN_CLOCK_POWER_BASE 0xE0100000
#define CLK_DIV0_OFFSET 0x300
uint pclk_msys_ratio = ((div>>12) & 0x7);
hclk = get_HCLK();
return hclk/(pclk_msys_ratio+1);
这个函数的意思就是获得PCLK_PSY的时钟是66M,因为如果万一PCLK改变,直接调用函数就行。
}
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val; //将得到的CNT写入计数器
/* auto load, manual update of Timer 4 */
timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;清零,更新
#define TCON_4_AUTO (1 << 22) /* auto reload on/off for Timer 4 */
#define TCON_4_UPDATE (1 << 21) /* manual Update TCNTB4 */
TCON_4_ONOFF (1 << 20) /* 0: Stop, 1: start Timer 4 */
#define COUNT_4_ON (TCON_4_ONOFF*1)
/* auto load, start Timer 4 */
timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
打开定时器
timestamp = 0;
return (0);
}
4.int env_init(void)
1)env_init,看名字就知道是和环境变量有关的初始化。
2)为什么有很多env_init函数,主要原因是uboot支持各种不同的启动介质(譬如norflash、nandflash、inand、sd卡•••••),我们一般从哪里启动就会把环境变量env放到哪里。而各种介质存取操作env的方法都是不一样的。因此uboot支持了各种不同介质中env的操作方法。所以有好多个env_xx开头的c文件。实际使用的是哪一个要根据自己开发板使用的存储介质来定(这些env_xx.c同时只有1个会起作用,其他是不能进去的,通过x210_sd.h中配置的宏来决定谁被包含的),对于x210来说,我们应该看env_movi.c中的函数。
3)经过基本分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境变量)。当前因为我们还没进行环境变量从SD卡到DDR中的relocate,因此当前环境变量是不能用的。
4)在start_armboot函数中(776行)调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡中去读取。
5.static int init_baudrate (void)
{
1)init_baudrate看名字就是初始化串口通信的波特率的。
2)getenv_r函数用来读取环境变量的值。用getenv函数读取环境变量中"baudrate"的值(注意读取到的不是int型而是字符串类型),然后用simple_strtoul函数将字符串转成数字格式的波特率。
3)baudrate初始化时的规则是:先去环境变量中读取"baudrate"这个环境变量的值。如果读取成功则使用这个值作为环境变量,记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功则使用x210_sd.h中的的CONFIG_BAUDRATE的值作为波特率。从这可以看出:环境变量的优先级是很高的。
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;
gd里面的波特率和bd里面的波特率是一致的。
return (0);
}
6.int serial_init(void)
{
1)serial_init看名字是初始化串口的。(疑问:start.S中调用的lowlevel_init.S中已经使用汇编初始化过串口了,这里怎么又初始化?这两个初始化是重复的还是各自有不同?)
2)SI中可以看出uboot中有很多个serial_init函数,我们使用的是uboot/cpu/s5pc11x/serial.c中的serial_init函数。
3)进来后发现serial_init函数其实什么都没做。因为在汇编阶段串口已经被初始化过了,因此这里就不再进行硬件寄存器的初始化了。
serial_setbrg();
void serial_setbrg(void)
{
DECLARE_GLOBAL_DATA_PTR;
int i;
for (i = 0; i < 100; i++);
}
return (0);
}
7.int console_init_f (void)
{
1)console_init_f是console(控制台)的第一阶段初始化。_f表示是第一阶段初始化,_r表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,因此将完整的一个模块的初始化分成了2个阶段。(我们的uboot中start_armboot的826行进行了console_init_r的初始化)
2)console_init_f在uboot/common/console.c中,仅仅是对gd->have_console设置为1而已,其他事情都没做。
gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE//没定义
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT;
#endif
return (0);
}
8.static int display_banner (void)
{
1)display_banner用来串口输出显示uboot的logo
2)display_banner中使用printf函数向串口输出了version_string这个字符串。那么上面的分析表示console_init_f并没有初始化好console怎么就可以printf了呢?
3)通过追踪printf的实现,发现printf->puts,而puts函数中会判断当前uboot中console有没有被初始化好。如果console初始化好了则调用fputs完成串口发送(这条线才是控制台);如果console尚未初始化好则会调用serial_puts(再调用serial_putc直接操作串口寄存器进行内容发送)。
4)控制台也是通过串口输出,非控制台也是通过串口输出。究竟什么是控制台?和不用控制台的区别?实际上分析代码会发现,控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送、接收•••),控制台的通信函数最终会映射到硬件的通信函数中来实现。uboot中实际上控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot中用没用控制器其实并没有本质差别。
5)但是在别的体系中,控制台的通信函数映射到硬件通信函数时可以用软件来做一些中间优化,譬如说缓冲机制。(操作系统中的控制台都使用了缓冲机制,所以有时候我们printf了内容但是屏幕上并没有看到输出信息,就是因为被缓冲了。我们输出的信息只是到了console的buffer中,buffer还没有被刷新到硬件输出设备上,尤其是在输出设备是LCD屏幕时)
6)U_BOOT_VERSION在uboot源代码中找不到定义,这个变量实际上是在makefile中定义的,然后在编译时生成的include/version_autogenerated.h中用一个宏定义来实现的。
printf ("\n\n%s\n\n", version_string);
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
#define U_BOOT_VERSION "U-Boot 1.3.4"
#define CONFIG_IDENT_STRING " for x210"
void printf (const char *fmt, ...)
{ puts (printbuffer);
}
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 serial_puts(const char *s)
{
while (*s) {
serial_putc(*s++);
void serial_putc (const char c)
{
if (!(gd->flags & GD_FLG_RELOC) || !serial_current) {
struct serial_device *dev = default_serial_console ();
dev->putc (c);
return;
}
serial_current->putc (c);
}
}
}
}
}
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");
#else
debug("\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);
#endif
open_backlight();//lqm.
//open_gprs();
return (0);
}
9.int print_cpuinfo(void)
这些信息都是print_cpuinfo打印出来的。
回顾ARM裸机中时钟配置一章的内容,比对这里调用的函数中计算各种时钟的方法,自己去慢慢分析体会这些代码的原理和实现方法。这就是学习。
#ifdef CONFIG_DISPLAY_BOARDINFO
10.int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)
printf("\nBoard: VOGUESV210\n");
#else
printf("\nBoard: X210\n"); //打印X210
#endif //CONFIG_VOGUES
#else
printf("\nBoard: X210\n");
#endif
return (0);
}
#endif
11.static int init_func_i2c (void)
没有用到
{
puts ("I2C: ");
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
puts ("ready\n");
return (0);
}
#endif
12.int dram_init(void)
1)dram_init看名字是关于DDR的初始化。疑问:在汇编阶段已经初始化过DDR了否则也无法relocate到第二部分运行,怎么在这里又初始化DDR?
2)dram_init都是在给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息,以便uboot中使用内存。
3)从代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组。
{
DECLARE_GLOBAL_DATA_PTR;
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
#define SDRAM_BANK_SIZE 0x10000000 /* 512 MB lqm*/
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE
#define PHYS_SDRAM_2 MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */
#define PHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE
#define MEMORY_BASE_ADDRESS 0x30000000
#define MEMORY_BASE_ADDRESS2 0x40000000
#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;
#endif
return 0;
}
13.static int display_dram_config (void)
1)看名字意思就是打印显示dram的配置信息。
2)启动信息中的:(DRAM: 512 MB)就是在这个函数中打印出来的。
3)思考:如何在uboot运行中得知uboot的DDR配置信息?uboot中有一个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以得知DDR的配置信息。
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000
{
int i;
#ifdef DEBUG//这个不管
puts ("RAM Configuration:\n");
for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
print_size (gd->bd->bi_dram[i].size, "\n");
}
#else
ulong size = 0;
for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
------------------------------------init_sequence总结------
都是板级硬件的初始化以及gd、gd->bd中的数据结构的初始化。譬如:
网卡初始化、机器码(gd->bd->bi_arch_number)、内核传参DDR地址(gd->bd->bi_boot_params)、Timer4初始化为10ms一次、波特率设置(gd->bd->bi_baudrate和gd->baudrate)、console第一阶段初始化(gd->have_console设置为1)、打印uboot的启动信息、打印cpu相关设置信息、检查并打印当前开发板名字、DDR配置信息初始化(gd->bd->bi_dram)、打印DDR总容量。
总结回顾:本节课结束后已经到了start_armboot的第487行。下节课开始继续往下看。
----------------------------------