***********************************
*******day:2014/10/14**************
************uboot******************
***********************************
1.为什么要有uboot?
2.uboot是用来干嘛的?
3.uboot是怎么工作的?
4.uboot结束后的结果是怎么样的?
为了回答以上的问题,或许问题还不止这些,根据我个人的理解来谈谈,请观看者注意版权问题哈。
起初龙芯的工程师跟我讲uboot的时候,我还搞不明白什么是uboot的,当时不懂事,学习之后我才明白什么是uboot的,我就举个比喻吧:就好比如你要吃西瓜,那你吃西瓜那好歹得有把西瓜刀菜刀之类的工具吧?难道你直接用啃的吗?这个西瓜刀就是你在吃到西瓜肉之前的uboot。所以对于为什么要有uboot,你应该懂了吧,只不过这个uboot不是用来切西瓜的,而是用来加载和启动我们传说中的内核的。至于什么是内核?等我写完uboot再睡会,然后再写关于内核这巨人。
那到底uboot是怎么样来启动和加载内核的呢??你看,这里又有新问题了吧?别紧张,在此之前,必须要先了解下uboot的工作原理,天啊,又是工作原理,别晕,其实当初我也晕了。其实,soc一上电,uboot瞬间就开始工作了,IROM里的固化代码会把nandflash里的前16K代码拷贝到IRAM里来执行,在这里实现重定位,这个IRAM的0x340000地址就是运行地址,记住这个前16K哈。为什么呢?拿个西瓜啃,慢慢往下看。
在拿到uboot的原始源码之后,记住,你要看看这个源码到底有没有支持你的开发板,如果没有就得重新找找其他版本的源码,由于uboot这种小玩意是跟开发板紧密相连的,所以你要根据你的开发板来做一定的移植,也就是针对开发板弄一个特定的uboot,对了,这个uboot的意思我的理解就是:u(you) 启动。
拿到源码直接定位到startS,你可以看到异常向量表之类的汇编代码,其实第一阶段就是用汇编写的,所以总结下第一阶段的过程:
1.设置异常向量表 一开始就跳到了reset那里了
2.关闭中断,主要是关闭F和I位,然后进入SVC32模式,记住,一定要在这种模式下 set the cpu to SVC32 mode
3.cpu初始化 bl cpu_init_crit 在这里主要是关闭缓存和MMU,至于为什么要关闭缓存,你想想,如果不关闭的话,CPU是从缓存里去指令的,这样做的原因就是因为匹配CPU的速度,为了乖乖的让CPU从内存里取指令,只好先关闭咯
4.关闭开门狗,关闭VIC,其实这里关闭了VIC的话,上面的F I 就没什么必要了,你想想,老大都被抓了,小弟还有勇气活吗?还有开门狗,其实这个狗真的很厉害,在你程序跑飞的时候可以帮你挽救系统。但在这时,你如果不关的话,它随时会让你的CPU一直复位的,除非你喂狗,但是你想想,有可能喂狗吗?
5.串口初始化,这里是为了调试在终端看信息用的,总不能用望远镜吧。
6.时钟初始化,这个时钟初始化不是说调现在几点几点的,而是为整个开发板提供工作频率的。
7.内存初始化。这个内存初始化一般都是跟原厂要的代码,比较复杂,主要是为了后面的重定位用的。
//注意注意:广大的中国市民:6 和 7 在源码里默认是没有帮你初始化的,所以你要做的移植就是:是这样的:把#if 后面的宏定义改成1就行了。也就是说,这个是没有被编译进去的,所以你这时候要做的就是改Makefile,添加:SOBJS := lowlevel_init.o mem_setup.o 但前提是你要修改你的内存初始化的代码---->跟原厂要。一定要记住要确保这段代码在nandFlash的前16K,怎么确保呢?这个问题问得好:你可以打开uboot.dis可以看到段的分区:如下:只要把代码放在text段里就行了
.text :
{
cpu/arm_cortexa8/start.o (.text)
board/samsung/fs210/lowlevel_init.o (.text)
board/samsung/fs210/mem_setup.o (.text)
board/samsung/fs210/nand_cp.o (.text)
*(.text)
}
8.栈的初始化。一定要注意:在进入C语言代码之前一定要确保栈已经初始化好了,否则,你后果自负。
9.重定位(这个指的是实现uboot的自重定位--->nandflash--->DDRII)这里面有点深奥,扯到Flash和DDRII的内容,在这里你只要先知道有这个步骤就行了,咦?什么,uboot是在Flash里的?没错,uboot在制作好的时候就是烧在Flash里的:这里给出平时烧录的步骤
烧录uboot://说明:这个是用tftp协议来下载的,所以,你的uboot必须要移植好,使其能支持tftp和nand erase 和nand write等命令,当然,这是比较后的知识了。其实也不是很后啦,就是在第二阶段做的事而已。
cp u-boot.bin /tftpboot/
uboot1.3.4:
tftp 0x20008000 u-boot.bin
nand erase 0x0 0x40000
nand write 0x20008000 0x0 0x40000
//当实现重定位之后,(把整个u-boot拷贝考DDRII里)在DDRII里的第一阶段的u-boot代码就不会被重新执行,
//此时的pc指针自动指向第二阶段的代码,然后执行
//之前的在上电启动的时候,u-boot被拷贝到nandflash里,由于nandflash不具备执行指令的功能,但能保证掉电后不丢失数据,所以在第一阶段里要实现u-boot把自己整个拷贝到DDRII里来执行。DDRII具有执行指令的功能
10.清bss段。这个嘛,顺应潮流,清吧。就当做是清垃圾吧,人人爱卫生,生活更美好
在进行清bss段之前,其实还有事情要做,也就是在DDRII里分配栈内存,也是预留一些内存,比如预留128K给环境变量
啊,等等。这些预留的空间会在以后用到,在这只要知道就行了。
11.跳转到第二阶段(c语言代码),//所以在之前说的,一定要在跳转到C语言代码之前初始化好栈。不然,、、、
ldr pc, _start_armboot @ jump to C code
_start_armboot: .word start_armboot
*******************************************************************************************************
第二阶段:从source insight里跳转,即可以看到start_armboot是在board.c里面的,那么第二阶段做了什么呢?
1.初始化一个全局变量指针 gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//这两个指针就是指向了第一阶段预留的空间的某一个位置
gd->bd->bi_arch_number = MACH_TYPE_SMDKC100; //开发板的id号码 1826---》在内核中要用到
//内核里其实也存在一个ID,它会跟你这个id进行比较,如果发现不对,而此时你又没有通过set machid 来给内核的话,内核就会找不到你的这个开发板,这时候内核就会启动不起来。而这个的作用主要是确保你的开发板跟内核是保持一致的。
gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100; //传递给内核的数据所存放的位置
//这个变量就是你的uboot传递给内核的参数所在的内存位置,这个就是一个约定,你放在这个位置,内核也知道
//这个位置去取,如果你存放参数的位置不对,这时候内核取不到数据,你想他会怎么样,它不哭不闹不上吊,直
//接就死给你看,你拿他没办法的。
//这时候的参数又分为两种情况:(在本节来说,有点扯远了)
1、默认的参数设置:当内核发现,uboot并没有通过set bootargs给它传递参数时,内核这时它不哭,它就去找
看它本身有没有默认的参数,比如serveriP 等,如果有,谢天谢地,它直接拿来用。这个默认参数一般都设置在
开发板的配置文件比如smdkc100.h里,那怎么找呢?咦,跟读代码你可以发现在初始化序列里(下面)有这样的一
个函数:env_init, /* initialize environment */
//gd->env_addr = (ulong)&default_environment[0];在这里就是使用了默认的参数设置
//gd->env_valid = 1; 这时候你就要问了?这个参数不是存放在第一阶段预留的DDRII里吗?那一掉电不就是
//丢失了数据了吗?为什么在下次启动的时候在没有传参的时候还会加载??问得好,其实这个默认参数时在
//uboot的.data段,当nandflash中没有有效数据时就会优先使用默认的
2.你通过set bootargs给内核传递参数
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.7.99:/opt/filesystem ip=192.168.7.189 init=/linuxrc
参数说明:
console=xxx : 告诉内核printk的调试信息是通过什么设备打印出来
console=ttySAC0,115200
root=xxx: 告诉根文件系统在哪里了
root=/dev/nfs : 根文件系统存在网络远端
nfsroot=ip:path : 详细地址 这个ip地址就是你Ubuntu的地址
ip=192.168.7.189 : 系统启动之后,静态分配ip
root=/dev/nfs + nfsroot=xxx + ip=xxx
init=xxx: 告诉内核祖先进程的可执行代码是什么
init=/linuxrc
//广大的中国市民:这里参数的设置,其实目的是为了让你在网络远端下载的,其实都是要通过网络的,但有没有直接一上电就不需要从网络下载呢?是有的,其实就是把内核,文件系统直接都烧录在nandflash里,当然这里只是稍微提下
在跟读代码的时候可以看到这样一个循环:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
这个循环实际上就是初始化序列,而可以看到其实init_sequence是一个函数指针数组(这个有点饶吧:其实就是把指向函数的指针存放在数组里,也就是这个数组存放的都是指向函数的指针)
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT) //这个宏定义显得很有意思,这种可以一次性判断多个宏是否被定义,所以要使用该初始化函数,就必须要定义这个宏
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */值得注意的是:在此函数之前不允许有任何的打印
#endif
dram_init, /* configure available RAM banks */
...
};
2.nandflash的初始化 前面提到过,你要烧录uboot,就必须要用nand erase等命令,而这些命令使用的前提就必须
要初始化nandflash ,先看下nandflash的命令用法:
nand命令:
nand read[.jffs2] - addr off|partition size 这个[.jffs2]可以先不用理他 这是一种文件系统。
nand read 0x20008000 0x0 0x100000
ARM地址 nandflash的地址 大小 从nandflash的地址读大小为0x100000(1M)的代码量到0x20008000这个地址里
nand erase 0x100000 0x400000
nand write[.jffs2] - addr off|partiton size
nand write 0x20008000 0x100000 0x400000
同nand read
那怎么样才能初始化呢?跟读代码可以发现:
#if defined(CONFIG_CMD_NAND)//必须先定义这个宏
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
这样算初始化了??有个init??那你就错了,其实这个代码跟进去,它里面做的事情就是帮你计算内存是多大而已,
printf("%u MiB
", size / 1024);
所以,nandflash的代码和实现uboot重定位的代码需要自己写的,因为每个开发板可能寄存器什么的有可能不一样,所以在这就不列出代码了,在这里只是要提一点://你重定位的地址一定要跟你链接地址要一致,不然内核找不到
在上面提到:那个环境变量为什么在下次启动的时候在没有传参的时候还会加载??
首先你要明确的一点就是:nandflash本身存放的数据在掉电的情况下是不会丢失数据的,
再者,nandflash存放数据和取数据也很方便,只要三个命令即可,即nand erase / nand write /nand read
经过上面的nandflash的移植,就可以使用啦,
所以,这个涉及到环境变量的重定位问题,跟读代码我们可以发现这样的一个函数:
/* initialize environment */
env_relocate ();//环境变量的重定位
|
env_relocate_spec ();
|
if(gd->env_valid == 1) {
env_ptr = tmp_env1;
}
可以看出,其实在DDRII里预留的128K的环境变量等参数其实是在nandflash的CONFIG_ENV_OFFSET为起始地址的128K重定
位到DDRII来执行的,所以每次在掉电后开机,都能执行到环境变量参数,而每次set 什么的都是在DDRII里修改的
这时候通过save就可以保存到nandflash里了,方便吧。
至于nandflash要用到的宏定义我在这里就直接给出了,仅供参考:
#define CONFIG_ENV_IS_IN_NAND 1
#define CONFIG_CMD_NAND 1
#define CONFIG_ENV_SIZE (256 << 10) /* 128KiB, 0x20000 */
#define CONFIG_ENV_OFFSET (512 << 10) /* 512KiB, 0x80000 */
#define CONFIG_SYS_MAX_NAND_DEVICE 1 //nand设备数量
#define CONFIG_SYS_NAND_MAX_CHIPS 1 //芯片的数量
#define CONFIG_SYS_NAND_BASE 0xB0E00000 // nandflash控制器的基地址
#define CONFIG_NAND_S5PV210 1 // mtd支持s5pv210的nand操作,用于控制s5pv210.c的编译
#define CONFIG_NAND_BL1_8BIT_ECC 1
#define CFG_NAND_HWECC 1
#define CONFIG_NAND_PAGES_IN_BLOCK 64 // 每个block有多少个页
3.网卡的初始化,使其支持tftp协议来在网络远端(pc机Ubuntu的/tftpboot)下载uboot,uIamge,filesystem等
这个板子用的是S5PV210是带网卡的,DM9000内核也支持,所以只要稍微移植就行了。为什么呢?这是因为在人群
中多看了一眼??开玩笑,跟代码可以发现这样的猫腻:
static void dm9000_get_enetaddr(struct eth_device *dev)
{
#if !defined(CONFIG_DM9000_NO_SROM)//其实这个宏定义是有定义的,所以默认情况下DM9000是没有获取到mac地址的
int i;
for (i = 0; i < 3; i++)
dm9000_read_srom_word(i, dev->enetaddr + (2 * i));
#else //所以在这里需要自己手动的增加以下代码
eth_getenv_enetaddr("ethaddr", dev->enetaddr); //从环境变量中获取mac地址
#endif
}
如果你觉得你只要做到这一关键的一步,那你就错了,其实这只是DM9000的驱动代码而已,然后以太网呢
?在board.c中调用的eth_initialize中其实调用了board_eth_init,但实际上这个board_eth_init是没
有的,所以需要我们来移植。在这个函数里来调用dm9000的驱动代码
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_DRIVER_DM9000
rc = dm9000_initialize(bis);//调用dm9000的驱动
|
/* Load MAC address from EEPROM */
dm9000_get_enetaddr(dev);
#endif
return rc;
}
但是你想,这样够了吗?eth_initialize你跟进去其实可以发现这个函数其实是用来调用dm9000的,但实际上以太网的初始化还是没有调用的,所以在board.c里需要添加以太网的初始化代码eth_init(gd->bd);
对于以太网和dm9000,那些宏定义,其实是根据跟源码来得知要定义哪些宏定义,在此,我就直接给出来啦:
//for dm9000,添加如下内容
#define CONFIG_CMD_NET 1 //支持网络命令
#define CONFIG_NET_MULTI 1
#define CONFIG_CMD_PING 1 // 支持ping命令
#define CONFIG_DRIVER_DM9000 1 //需要编译dm9000的驱动
#define CONFIG_DM9000_BASE 0x88000000 // dm9000的基地址 这个基地址是根据不同硬件来确定的
#define DM9000_IO CONFIG_DM9000_BASE //dm9000的IO地址 这个也是同上
#define DM9000_DATA (CONFIG_DM9000_BASE + 4) //dm9000的数据地址 这个也是同上
#define CONFIG_DM9000_USE_16BIT //数据位宽
#define CONFIG_DM9000_NO_SROM 1 // 网卡中不会自带rom用于存放mac地址
4.进入一个死循环,倒计时,这个倒计时是由系统定时器提供的,提供10ms的基准,
for (;;) {
main_loop ();
}
这个代码就不跟下去了,太恶心了,有兴趣的就跟吧,其实表现出来的就是五秒倒计时,在五秒内按任何键中止倒计时,否则,,,,就加载内核啦,当然此时要有内核放在/tftpboot里或者之前烧录 了内核镜像在nandflash的分区里面,不然,呵呵、、
至此,我个人觉得已经解决了以上提出来的问题,至于uboot结束后的结果是什么?个人觉得成功引导内核启动后接下来就没他的事了,只要启动了内核或者可以中断五秒进入可以设置的时候,uboot就应该say ヾ( ̄▽ ̄)Bye~Bye~了
要不然,你的uboot估计要重新检查了,没事,医疗费几十万而已啦、、、