uboot笔记

1.为什么要有uboot
(1)uboot主要作用:启动操作系统内核
(2)uboot还要负责部署整个计算机系统
(3)uboot中还要操作flash等板子上硬件的驱动
(4)uboot还得提供一个命令行界面供人来操作
2.计算机系统主要核心部件
(1)CPU(运算器+控制器)、外部存储器(Flash/硬盘)+ 内部存储器(DDR SDRAM/SRAM)
(2)典型的PC机部署:BIOS部署在PC机主板上(随主板出厂时已经预制了),操作系统部署在硬盘上,内存在掉电时无作用,
     CPUZ在掉电时不工作。
(3)启动过程:PC上电后先执行BIOS程序(实际上PC的BIOS就是NORFLASH,可以直接被启动),BIOS程序负责初始化DDR内存
     ,负责初始化硬盘,然后从硬盘上将OS镜像读取到DDR中,然后跳转到DDR中去执行OS直到启动(OS启动后BIOS就无用了)。
3.典型嵌入式Linux系统启动过程
(1)嵌入式系统的部署和启动都是参考PC机的。只是设备上有一些差别
(2)典型嵌入式系统的部署:bootloader:uboot部署在flash(能作为启动设备的flash)上,OS部署在flash(嵌入式系统中
     用flash代替了硬盘)上,内存在掉电时无作用,CPU在掉电时不工作。需要注意的是:uboot部署的flash和os部署的flash
     可以是同一个,也可以分开。不过现在都是用一个flash了,节约成本。
(3)启动过程:嵌入式系统上电后先执行uboot、然后Uboot负责初始化DDR,初始化flash,然后将OS从flash中读取到DDR中,然后
    启动OS(OS启动后uboot就无用了)。
总结:嵌入式系统和pc机的启动过程几乎没有两样,只是BIOS成了uboot,硬盘成了flash。
4.Android系统启动过程
(1)Android系统的启动和Linux系统几乎一样,只是在内核启动后加载根文件系统后不同了。
(2)可以认为启动分为2个阶段:第一个阶段是uboot到OS启动;第二个阶段是OS启动后到rootfs加载到命令行执行。
    Android的启动和Linux的差别在第二个阶段。
5.uboot从哪里来
(1)uboot是SourceForge上的开源项目
(2)uboot项目的作者:一个德国人最早发起的项目
(3)uboot就是由一个人发起,然后由整个网络上所有感兴趣的人共同维护发展而来的一个bootloader。
(4)uboot经过多年发展,已经成为事实上的业内bootloader标准。
(5)早期的uboot的版本号类似于这样:uboot1.3.4,后来变成了:uboot-2010.06。新版本与旧版本的核心部分几乎没有变化,
    越新的版本支持的开发板越多而已。
(6)uboot就是universal bootloader(通用的启动代码),修改代码后才可以在开发板上用。
(7)uboot的出现是一种必然,如果没有uboot也会有另外一个bootloader来代替。

6. uboot必须解决哪些问题
(1)自身可开机直接启动
    一般的Soc都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等......uboot要能够开机启动,必须根据具体的
    Soc的启动设计来设计uboot。支持SD卡启动,我们就把uboot放在SD卡里面,支持Norflash启动我们就把uboot放在Norflash里面。
    uboot必须进行和硬件相对应的代码级别的更改和移植,才能够保证可以从相应的启动介质启动。
    uboot中的第一阶段的start.s文件中具体处理了这一块。
(2)能够引导操作系统内核启动并给内核传参
    uboot的终极目标就是启动内核
    linux内核在设计的时候,设计为可以被传参。也就是说我们可以在uboot中事先给linux内核准备一些启动参数放在内存中特定位置然后
    传给内核,内核启动后会到这个特定位置去取uboot传给他的参数,然后在内核中解析这些参数,这些参数将被用来指导Linux内核的启动过程。
(3)能提供系统部署功能
    uboot必须能够被人借助而完成整个系统(包括uboot、kernel、rootfs等镜像)在flash上烧录下载工作。
    裸机教程中刷机就是利用uboot中的fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。
(4)能够进行soc级和板级硬件管理
    uboot中实现了一部分硬件的控制能力(uboot中初始化了一部分硬件),因为uboot为了完成一些任务必须让这些硬件
    工作。譬如uboot要实现刷机必须能驱动iNand,譬如Uboot要在刷机时LCD上显示进度条就必须能驱动LCD,譬如uboot能够
    通过串口提供操作界面就必须驱动串口。uboot要实现网络功能就必须驱动网卡芯片。
    什么是Soc级:就是Soc内部外设(譬如串口)。什么是板级:就是SOC外面开发板上面的硬件(譬如网卡、iNand)
(5)uboot的“生命周期”
    指Uboot什么时候开始运行,什么时候结束运行。本质上是一个裸机程序(不是操作系统),单线程的。操作系统是多线程的
    uboot可以Ping通主机,但是主机是ping不通uboot的,原因是Uboot是一个裸机程序,单线程的
    裸机程序有个特点,一旦uboot开始SoC就会单纯运行uboot(意思就是Uboot运行的时候别的程序是不可能运行的,它对CPU是独占的),一旦
    Uboot结束运行则无法再回到Uboot(Uboot启动内核后Uboot自己本身就死了,要想再次看到Uboot界面只能重启系统,重启并不是复活了
    刚才的Uboot,重启只是Uboot的另一生)。
    Uboot的人口和出口:Uboot的入口就是开机自动启动,Uboot的唯一出口就是启动内核。一启动内核就死掉了。
    Uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到Uboot命令行继续执行Uboot命令,而启动
    内核命令一旦执行就回不来了。
总结:一切都是为了启动内核
7. Uboot的工作方式
(1)Uboot的本质就是一个裸机程序,uboot.bin大概在180kB~400kB
(2)Uboot本身是一个开源项目,由若干个.c文件和.h文件组成,配置编译之后会生成一个uboot.bin,这就是uboot这个裸机程序的
    镜像文件。然后这个镜像文件被合理的烧录到启动介质中拿去给soc去启动。也就是说uboot在没有运行时表现为uboot.bin,
    一般躺在启动介质中。
(3)uboot运行时会被加载到内存中然后一条指令一条指令的拿给CPU去运行。
(4)普通的裸机程序运行起来就直接执行了,执行效果和代码相关。有些程序需要和人进行交互,于是程序中就实现了一个shell(
    shell就是提供人机交互的一个界面),uboot就实现了一个shell。
(5)注意:shell并不是操作系统专有的,和操作系统一点关系都没有。Linux中打开一个终端后得到了一个shell,可以输入命令回车执行,
    uboot中的shell工作方式和Linux中的终端shell非常像(其实几乎是一样的,只是命令集不一样。)
(6)Uboot启动后大部分时间和工作都是在shell下完成的(譬如Uboot要部署系统要在shell下输入命令、要设置环境变量也得在
    命令行下,要启动内核也要在命令行下敲命令)。

8.Uboot的环境变量
(1)与操作系统的环境变量几乎完全相同,设计时借鉴了操作系统的设计理念(命令行借鉴了Linux终端命令行,环境变量借鉴了操作
    系统的环境变量,Uboot的驱动管理几乎完全照抄)。可以这么说Uboot是Linux的衍生品。
(2)环境变量有什么用:可以被认为是系统的全局变量,环境变量名都是系统内置的(认识就认识,不认识就不认识,但也有一部分环境变量是
    自己添加的,自己添加的系统就不认识,但是我们的自己的程序认识。)
    默认的环境变量如PATH,系统或者我们的自己的程序在运行时可以通过读取环境变量来指导程序的运行。这样设计的好处是
    灵活,譬如我们要让一个程序更改运行方法,不用去重新修改程序代码再重新编译运行,而只要修改相应的环境变量就可以了,
    可以这么理解,环境变量就是程序运行时的一些配置属性。
例如修改环境变量bootdelay
hisilicon # printenv 
baudrate=115200
ethaddr=00:00:23:34:45:66
ipaddr=192.168.1.10
netmask=255.255.255.0
bootfile="uImage"
serverip=192.168.1.100
bootcmd=sf probe 0;sf read 0x82000000 0x100000 0x300000;bootm 0x82000000
bootargs=mem=32M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1024K(boot),3072K(kernel),12288K(rootfs)
bootdelay=2
stdin=serial
stdout=serial
stderr=serial
verify=n
ver=U-Boot 2010.06 (Sep 17 2018 - 20:12:12)

Environment size: 438/262140 bytes
把环境变量bootdelay该成5
hisilicon # setenv bootdelay 5
设置完后printenv查看一下
hisilicon # printenv 
baudrate=115200
ethaddr=00:00:23:34:45:66
ipaddr=192.168.1.10
netmask=255.255.255.0
bootfile="uImage"
serverip=192.168.1.100
bootcmd=sf probe 0;sf read 0x82000000 0x100000 0x300000;bootm 0x82000000
bootargs=mem=32M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1024K(boot),3072K(kernel),12288K(rootfs)
stdin=serial
stdout=serial
stderr=serial
verify=n
ver=U-Boot 2010.06 (Sep 17 2018 - 20:12:12)
bootdelay=5

Environment size: 438/262140 bytes
然保存save一下
hisilicon # saveenv 
Saving Environment to SPI Flash...
Erasing SPI flash, offset 0x00080000 size 256K ...done
Writing to SPI flash, offset 0x00080000 size 256K ...done
重启看一下效果,即可看到从5开始到计时,

9.Uboot的常用命令
(1)类似Linux终端的行缓冲命令行
    行缓冲的意思就是:当我们向终端命令行输入命令的时候,这些命令没有立即被系统识别,而是被缓冲到一个缓存区(也
    就是系统认为我们还没有输入完),当我们按下回车键后系统就认为我们输入完了,然后将缓存区中的所有刚才输入的作为命令拿去分析处理。
(2)Linux终端设计有三种缓冲机制:无缓冲(输入一个处理一个,不要按回车)、行缓冲(按回车就处理)、全缓冲(缓存区满了才去处理,按回车都没用)    
    常见的是行缓冲。
(3)有些命令有简化的别名
    例如:printenv命令可以简化为print/pri等等
    help和?等价,可以看到所有的命令
(4)有些命令可以带参数
    Uboot的每个命令都有事先规定好的各种格式,例如:help ping
(5)命令中的特殊符号(如单引号)
    Uboot的有些命令带的参数非常长,为了告诉Uboot这个非常长的而且中间有好多空格的东西是给他的一整个参数,
    所以用单引号将这个很长且中间有空格隔开的参数引起来。避免Uboot的命令行做出一些错误的判断。
    别的符号也许也有,如分号(两个命令之间用分号间隔),逗号
    中间有分号就要用单引号括起来,没有分号不括起来也行。
    set bootcmd 'sf probe 0;sf read 0x82000000 0x100000 0x300000;bootm 0x82000000'
    set bootargs mem=32M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1024K(boot),3072K(kernel),12288K(rootfs)
    Uboot的命令是IC厂商添加进去的,和Uboot没有关系,每家厂商按自己的习惯命名

10.print/printenv
    不带参数,打印出系统所有的环境变量
    注意:环境变量就好像程序的全局变量一样。程序中任何地方都可以根据需要去调用或者更改环境变量(一般都是调用),
    环境变量和全局变量不同之处在于:全局变量的生命周期是在程序的一次运行当中,开始运行时诞生程序结束时死亡,下次运行程序时从头开始;
    但是环境变量被存储在flash的另一块专门区域(flash上有一个环境分区),一旦我们在程序中保存了该环境变量,那么下次开机时该环境变量的值
    将维持上一次更改保存后的值。
(1)print命令不用带参数,作用是打印出系统中所有的环境变量。
(2)环境变量就好像程序的全局变量一样。程序的任何地方都可以根据需要去调用或
     更改环境变量(一般都是调用)。环境变量和全局变量不同之处在于:全局变量的生命周期是在
     程序的一次运行当中,开始运行时诞生程序结束时死亡,下次运行程序时从头开始;但是环境变量
     被存储在flash的另一块专门的区域(flash上有一个环境变量分区),一旦我们在程序中保存了该
     环境变量,那么下次开机时该环境变量的值将维持上一次更改保存后的值。
(3)系统启动时,flash中的uboot被拷贝到内存中,同时在内存中也维护一份环境变量,系统启动后工作用
    的是内存的里面的环境变量,内存里面的环境变量是在uboot启动时从flash中读过来的,只读一次,读过
    之后工作中就只管内存里面的这份环境变量,不管flash中的环境变量了,内存的这一份改完之后要执行save
    操作才会保存到flash中,如果不save下次开机从flash中读出来的还是原始的值,

         flash                  
     |------------|            
     |  rootfs    |
     |------------|
     |  kernel    |
     |------------|
     | 环境变量   |
     |------------|
     |    uboot   |
     |------------|
    
         
         
         内存    
     
     |------------|
     | 环境变量   |
     |------------|
     |    uboot   |
     |------------|

11. setenv/save
保存环境变量的更改,命令不带参数,直接执行。作用是将内存中的环境变量的值
同步到flash中环境变量的分区。注意:环境变量的保存是整体的覆盖保存,也就是说内存中
所有的环境变量都会整体的将flash中环境变量分区中原来的内容整体覆盖。不能改谁就保存谁,
原因是flash的块设备,块设备只能整块的读写,也就是说你改一个与改一群成本是一模一样的。
总结:彻底更改一个环境变量的值,需要2步:第一步set命令来更改内存中的环境变量,第二步
用save命令将其同步到flash中环境变量的分区。
12. ping 
网络测试指令,ping是测试开发板和主机之间的网络连接。
ipaddr变量表示开发板的IP地址
13.tftp
(1)uboot本身主要目标是启动内核,为了完成启动内核必须要能够部署内核,uboot为了部署内核就需要将内核镜像从
主机中下载过来然后烧录到本地flash中,uboot如何从主机(windows或者虚拟机ubuntu)下载镜像到开发板上,
主流方式是:fastboot和tftp。
fastboot是通过ubs线进行数据传输的,这个技术是进几年加入到uboot中的技术。
tftp的方式是通过有线网络的。典型的方式就是通过网络。
(2)tftp方式下载时实际上uboot扮演的是tftp客户端。主机windows或者虚拟机ubuntu中必须有一个tftp服务器,
然后将要下载的镜像文件放在服务器的下载目录中,然后开发板中使用uboot的tftp命令去下载即可。
(3)有些人习惯在windows中搭建tftp服务器,一般是用一些软件来搭建(如tftpd32,使用起来比较简单),
(4)我的虚拟机搭建的时候设置的tftp下载目录是/tftpboot,将要被下载的镜像复制到这个目录下。
(5)检查开发板uboot的环境变量,注意serverip必须为虚拟机ubuntu的ip地址
(5)然后在开发板的uboot下先ping通虚拟机ubuntu,然后再尝试下载:
     tftp 0x82000000   uImage_hi3518ev200  从服务器(Ubuntu)下载文件(uImage_hi3518ev200)到内存0x82000000
(6)镜像下载到开发板的DDR中后,uboot就可以用指令进行镜像的烧写,和网络没有关系了
    sf probe 0  先选中spiflash ,0代表第一个,板上可能有多个flash
    sf erase 0x100000 0x300000
    sf write 0x82000000 0x100000 0x300000
(7)如果你是用windows下的tftp服务器,那边uboot的serverip就要设置为和windows下tftp服务器的IP地址一样。
注意:整个过程中间环节比较多,实际做的时候可能最后会下载不下来,这时候可以能的问题非常多,第一步应该先保证uboot
和ubuntu可以ping通;第二部再保证ubuntu中的服务器搭建没错;第三步再实现tftp下载。
14. movi
(1)movi read 读取iNand到DDR上,movi write用来将DDR中的内容写入iNand中,
(2)movi read {u-boot | kernel}{addr}  解释:movi read表示必选,一对大括号{}括起来的部分必选一个,大括号中|表示多选
一。中括号[]表示可选参数(可有可无)
(3)指令有多种用法,比如:movi read u-boot 0x30000000,意思就是把iNand中的中的u-boot分区读出到DDR的0x30000000的起始
的位置。(uboot代码中将iNand分成了很多个分区,每个分区有地址范围和分区名,uboot程序程序操作中可以使用直接地址来操作
iNand分区,也可以使用分区名来操作分区,显然,u-boot就是分区名,分区对应一个地址段),注意这里的0x30000000也可以直接写作
30000000,意思是一样的(uboot的命令行中所有的数字都被默认当作16进制处理,不管你加不加0x都一样)
15. NandFlash操作指令nand
理解方法和操作方法完全类似于movi指令
16. 内存操作指令 mm mv md 
(1)DDR中是没有分区的(只听说过对硬盘、flash进行分区,没有听说过对内存进行分区),但是内存使用时要注意,千万不能越界踩到别人。
因为uboot是一个裸机程序,不像操作系统会由系统整体管理所有内存,系统负责分配和管理,系统会保证内存不会随便越界。
然后裸机程序中uboot并不管理所有内存,内存是散的随便用的,所以如果程序员(使用uboot的人)自己不注意就可能出现自己把
自己的数据给覆盖了。
(2)md :memory display显示内存的内容
md [.b .w .l] address [# of objects]  解释.b:按字节显示;.w按字显示;.l按四字节显示
默认为四字节显示
hisilicon # md 0x82000000
82000000: 56190527 4d482b7a 966ea05b 88d22800    '..Vz+HM[.n..(..
82000010: 00800080 00800080 b88c241b 00020205    .........$......
82000020: 756e694c 2e332d78 35332e34 00000000    Linux-3.4.35....
82000030: 00000000 00000000 00000000 00000000    ................
82000040: e1a00000 e1a00000 e1a00000 e1a00000    ................
82000050: e1a00000 e1a00000 e1a00000 e1a00000    ................
82000060: ea000002 016f2818 00000000 0028d288    .....(o.......(.
82000070: e1a07001 e1a08002 e10f2000 e3120003    .p....... ......
82000080: 1a000001 e3a00017 ef123456 e10f2000    ........V4... ..
82000090: e38220c0 e121f002 00000000 00000000    . ....!.........
820000a0: e59f4784 eb000055 e28f0f4a e8901c4e    .G..U...J...N...
820000b0: e590d01c e0400001 e0866000 e08aa000    ......@..`......
820000c0: e5da9000 e5dae001 e189940e e5dae002    ................
820000d0: e5daa003 e189980e e1899c0a e08dd000    ................
820000e0: e28da801 e3a05000 e28aa901 e154000a    .....P........T.
820000f0: 2a000016 e084a009 e28f9050 e15a0009    ...*....P.....Z.
hisilicon # md.b 0x82000000
82000000: 27 05 19 56 7a 2b 48 4d 5b a0 6e 96 00 28 d2 88    '..Vz+HM[.n..(..
82000010: 80 00 80 00 80 00 80 00 1b 24 8c b8 05 02 02 00    .........$......
82000020: 4c 69 6e 75 78 2d 33 2e 34 2e 33 35 00 00 00 00    Linux-3.4.35....
82000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
hisilicon # md.w 0x82000000
82000000: 0527 5619 2b7a 4d48 a05b 966e 2800 88d2    '..Vz+HM[.n..(..
82000010: 0080 0080 0080 0080 241b b88c 0205 0002    .........$......
82000020: 694c 756e 2d78 2e33 2e34 3533 0000 0000    Linux-3.4.35....
82000030: 0000 0000 0000 0000 0000 0000 0000 0000    ................
82000040: 0000 e1a0 0000 e1a0 0000 e1a0 0000 e1a0    ................
82000050: 0000 e1a0 0000 e1a0 0000 e1a0 0000 e1a0    ................
82000060: 0002 ea00 2818 016f 0000 0000 d288 0028    .....(o.......(.
82000070: 7001 e1a0 8002 e1a0 2000 e10f 0003 e312    .p....... ......
hisilicon # md.l 0x82000000
82000000: 56190527 4d482b7a 966ea05b 88d22800    '..Vz+HM[.n..(..
82000010: 00800080 00800080 b88c241b 00020205    .........$......
82000020: 756e694c 2e332d78 35332e34 00000000    Linux-3.4.35....
82000030: 00000000 00000000 00000000 00000000    ................
82000040: e1a00000 e1a00000 e1a00000 e1a00000    ................
82000050: e1a00000 e1a00000 e1a00000 e1a00000    ................
82000060: ea000002 016f2818 00000000 0028d288    .....(o.......(.
82000070: e1a07001 e1a08002 e10f2000 e3120003    .p....... ......
82000080: 1a000001 e3a00017 ef123456 e10f2000    ........V4... ..
82000090: e38220c0 e121f002 00000000 00000000    . ....!.........
820000a0: e59f4784 eb000055 e28f0f4a e8901c4e    .G..U...J...N...
820000b0: e590d01c e0400001 e0866000 e08aa000    ......@..`......
820000c0: e5da9000 e5dae001 e189940e e5dae002    ................
820000d0: e5daa003 e189980e e1899c0a e08dd000    ................
820000e0: e28da801 e3a05000 e28aa901 e154000a    .....P........T.
820000f0: 2a000016 e084a009 e28f9050 e15a0009    ...*....P.....Z.

md.b 0x82000000 10  显示16个字节
82000000: 04 a8 8f 02 d4 a1 a0 3e 04 00 00 00 03 b8 af 01    .......>........

(3)mw就是memory write,将内容写到内存中
mw [.b .w .l] address value [count]
mw.b 82000001 56    把第一个字节写成56
读回来看一下
md.b 82000001 1
82000001: 56    V     读回来是正确的,说明已经写进去了
(4)mm就是memory modify 修改内存中的某一块,说白了还是写内存。
mm [.b .w .l] address
mm.b 82000000
82000000: 04 ? 11  原来是04,修改为11,修改完回车自动跳到下一个字节。
修改完以后,敲入y
如果需要批量的逐个单元的修改内存,用mm最合适。如你只想写一个单元,用mw
17.启动内核指令:bootm、go
(1)uboot的终极目标就是启动内核,启动内核在uboot中表现为一个指令,uboot命令
行中调用这个指令就会启动内核(不管成功与否,所以这个指令就是一条死路,除了重启没有别的方法回到uboot里面来)。
(2)差别:bootm启动内核同时给内核传参,而go命令启动内核不给内核传参。
bootm其实才是正宗的启动内核的命令,一般情况下都用这个;go命令本来不是专为启动内核设计的,go命令内部其实
就是一个函数指针指向一个内存地址然后直接调用那个函数。go命令的实质就是pc直接跳转到一个内存地址去运行。
go命令可以用来在uboot中执行任何的裸机程序(有一种调试裸机程序的方法就是事先启动uboot,然后在uboot中去
下载裸机程序,用go命令去执行裸机程序)。
18. uboot的常用环境变量
uboot中的环境变量相当于全局变量,与全局变量的区别是uboot中环境变量可以保存。
19.环境变量如何参与程序运行
(1)环境变量有2份,一份在flash中,另一份在DDR中。uboot开机时一次性从flash中读取全部环境变量到DDR中作为环境变量
的初始化值。然后使用过程中都是使用DDR中这一份。用户可以用saveenv指令将DDR中的环境变量重新写入flash中去更新flash
中的环境变量。下次开机时又会从flash中再次读一次。
(2)怎么删除环境变量
有时候因为粗心把环境变量打错,如何删除
set botdelay 即可删除
(3)自动运行倒数时间:bootdelay
(4)网络设置:ipaddr serverip gatewayip netmask ethaddr
    ipaddr是开发板的本地IP地址
    serverip是开发板通过tftp指令去tftp服务器下载东西时,tftp服务器的IP地址。
    gatewayip是开发板的本地网关地址
    netmask是子网掩码
    ethaddr是开发板的本地网卡的mac地址,软件可以随意更改。
(5)自动运行命令设置:bootcmd
    uboot启动后会开机自动倒数bootdelay秒,如果没有人按下回车打断启动,则uboot会自动执行启动命令来启动内核。
    uboot开机自动启动时实际就是在内部执行了bootcmd这个环境变量的值所对应的命令集。开机自动启动其实
    就是执行了这个命令。
    bootcmd=movi read kernel 30008000; bootm 30008000 注意命令之间要用分号隔开,
            movi read kernel 30008000为从inand的kernel分区读取内容到内存的30008000地址,
            bootm 30008000 为从内存的30008000地址启动内核
    海思平台也有类似的
    set bootcmd 'sf probe 0;sf read 0x82000000 0x100000 0x300000;bootm 0x82000000'  
    注意要加单引号,有分号隔开说明是多条命令,加单引号说明是一体的,中间没有分号,可以加也可以不加单引号
            sf read 0x82000000 0x100000 0x300000把spi flash从地址0x100000开始长度为0x300000的内容读到内存地址0x82000000中
    如果你把bootcmd设置成bootcmd=printenv,那么开机启动就会自动打印环境变量   

20.uboot的获取途径
    3种获取途径:uboot官方、SoC官方、具体开发板的官方。
    
21.配置
(1)uboot和linux等复杂项目,都不能直接编译,都要先配置才能编译。
(2)uboot也要先配置,配置方法是:首先进入uboot源码的根目录,然后在根目录下执行:
单独编译uboot:
    待进入boot源代码目录后,执行以下操作
    make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- hi3518ev200_config 
    make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux-    //配置后只要执行这条就行
    将生成的u-boot.bin 复制到osdrv/tools/pc/uboot_tools/目录下
    运行./mkboot.sh reg_info_hi3518ev200.bin u-boot-ok.bin
    生成的u-boot-ok.bin即为可用的u-boot镜像
 make -j2   多核电脑可以用多线程编译 这里2就是2线程编译,四核的就-j4,八核就-j8
 这样会快一些。单核电脑直接make就行了
(3)不同版本的uboot或者同一版本不同人移植的uboot,可能目录结构和文件内容都有所不同。
可以自己根据需要去添加/删除/更改目录结构。
22.各文件介绍
(1).gitignore git工具的文件,git是一个版本管理工具。(类似的还有SVN),git是在Linux
上面诞生的。这个文件和git有关,和uboot本身无关的,不用去管。
(2)arm_config.mk
后缀是.mk,是一个Makefile文件,将来在某个Makefile中会去调用它。
(3)三个changelog文件,修改记录文件,该文件记录了这个uboot项目的版本变迁以及每个版本较上个
版本修改的记录。正式的项目都有这些记录的。主要是给维护uboot的人用的。
(4)config.mk
和arm_config.mk差不多性质
(5)COPYING
版权声明,uboot本身是GPL许可证的
(6)CREDITS 鸣谢 对uboot有贡献的人
(7)image_split
这是一个脚本,看说明是用来分割uboot.bin到BL1的,不是uboot原生的,暂时用不到,先不管。
(8)MAINTAINERS
维护者,就是当前在参与维护uboot编码的社区工作者。
(9)MAKEALL
一个脚本,应该是帮助编译uboot的
(10)Makefile
这个很重要,是uboot源代码的主Makefile,什么是主Makefile,因为每个文件夹里面还有子Makefile。
将来整个uboot被编译时就是这个Makefile管理编译的。
(11)mk 快速编译的脚本,其实就是先清理然后配置然后编译而已。
(12)mkconfig
这个很重要,是uboot配置阶段的主要配置脚本,uboot的可移植性很大程度就是靠这个配置脚本在维护的
(13)mkmovi
暂时不去管他,一个脚本,和iNand/SD卡启动有关
(14)README
所有的软件都有README,就是一个说明书
(15)rules.mk
少了这个文件编译是不能通过的,这个文件是我们uboot的Makefile使用的规则,本身非常重要,但是我们
不去分析他,不去看他。

总结:需要认真看的有两个mkconfig和Makefile。一个负责uboot的配置,一个负责编译。

23.uboot的源代码目录分析
(1)api
硬件无关的功能函数的API。uboot移植时基本不用管,这些函数是uboot本身使用的。
(2)api_examples
API相关的测试事例代码。
(3)board
board是板的意思,板就是开发板。board文件夹下每一个文件都代表一个开发板,这个文件夹下面放
的文件就是用来描述这一个开发板的信息的。board目录下有多少个文件夹,就表示当前这个uboot已经被
移植到多少个开发板上了(当前的uboot支持多少个开发板)。
问题一:board下有这么多文件夹,究竟如何确定具体使用的是哪一个?uboot在配置阶段会有一些手段帮助我们来
确定具体使用的是board目录下的哪一个文件夹。(想一想为什么不直接编译而要先配置)
问题二:开发板越来越多,board目录下文件夹越来越多不方便管控。于是乎uboot就新增了一种机制,可以在
board目录下不直接放开发板目录,而是在board下放厂家目录(vendor)下面。多了这层目录会影响配置阶段,
在uboot的配置阶段要注意配置时的路径深度和实际存放要对应,不然配置后编译时找不到文件编译就会失败。
最开始时目录下就是开发板名字,后来才改成厂商名字的,但是因为要向前兼容,同一个厂家原来还是外面的开发板
并没有挪移到厂家目录下面去。这样就造成后来的人不知道原委的感到很奇怪,感觉很混乱。

注意:uboot的配置阶段(其实就是根目录下面的mkconfig脚本和Makefile中配置有关的部分)主要解决的问题就是
在可移植性领域能够帮助我们确定具体的文件夹路径,然后编译时可以找到应该找到的文件,才能编译成功。因此b
oard目录下的不同会造成配置的不同。如果移植时没有注意这里肯定要失败。
(4)common
普遍的普通的,这个文件夹下放的是一些与具体硬件无关的普遍适用的一些代码。
譬如控制台实现,crc校验的,但是更多的主要是两类:一类是cmd开头的,是用来实现uboot的命令系统的;
另一类是env开头的,是用来实现环境变量的。
(5)cpu
这个目录是SoC相关的,里面存放的代码都是SoC相关初始化和控制代码(譬如CPU的,中断的、串口等SoC内部外设这个目录是SoC相关的,里面存放的代码都是SoC相关初始化和控制代码(譬如CPU的,中断的、串口等SoC内部外设
的,包括起始代码start.S也在这里面)。里面很多文件夹,每一个子文件夹就是一个SoC系列。这个文件夹是严格
和硬件相关的。因此移植时也是要注意的。
(6)disk
磁盘相关的,用不到,不用研究。
(7)doc
文档目录,里面存放了很多uboot相关文档。比较杂乱,几乎没啥用
(8)drivers
顾名思义,驱动。这里放的就是从linux源代码中扣出来的原封不动的linux设备驱动。
主要是开发板上必须用到的一些驱动,如网卡,Inand/SD卡,NandFlash等的驱动。
要知道,uboot中的驱动其实就是linux中的驱动,uboot在一定程度上移植了linux的驱动给
自己用。但是linux是操作系统而uboot只是个裸机程序,因此这种移植会有不同。
其实uboot中的驱动就是linux中的驱动的一部分。
(9)examples
示例代码,没用过
(10)fs
filesystem文件系统,这个也是从linux源代码中移植过来的,用来管理flash等资源
大家不要以为文件系统和操作系统是绑定的,其实不是,裸机里面也可以用文件系统。
(11)include
头文件目录,uboot和linux kernel在管理头文件时都采用了同一个思路,就是把所有的
头文件集中存放在include目录下,而不是头文件跟着自己对应的c文件。像这种大项目
把头文件集中放在一起才有意义。所以在uboot中头文件包含时路径结构要在这里去找。
(12)lib_xx
典型的lib_arm和lib_generic架构相关的库文件。
譬如lib_arm里面就是arm架构使用的一些库文件。lib_generic里是所有架构通用的库文件。
这类文件夹中的内容移植时基本不用管。
(13)libfdt
设备树有关的。
linux内核在3.4左右的版本的时候更改了启动传参机制,改用设备树来进行启动传参。

(16)nand_sp1
nand相关,不讲
 
(17)net
网络相关的代码,譬如uboot中的tftp nfs ping命令
(18)onenand开头的,是onenand相关的代码,是三星加的,标准的uboot中应该
是没有的。
(19)post 
没关注过,不知道干嘛的
(20)sd_fusing
这里面代码实现了烧录uboot镜像到SD卡的代码,后面要仔细研究的
(21)tools
工具类型的代码


22 sourceinsight的使用
23.Makefile解读
(1)uboot的版本号分为4个级别
VERSION = 2010       主版本号
PATCHLEVEL = 06      次版本号或补丁版本号
SUBLEVEL =           再次版本号
EXTRAVERSION =       另外附加的版本信息
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
这几行告诉我们,uboot的版本号分4个级别
这4个版本号用.分隔开共同构成了最终的版本号
(2)Makefile中的版本号最终生成了一个变量U_BOOT_VERSION,这个变量记录了Makefile
中配置的版本号。
(3)VERSION_FILE = $(obj)include/version_autogenerated.h    
就是根目录下的include,version_autogenerated.h是在编译的过程的中自动产生的,
源目录刚开始是没有的,编译过后的uboot就有了。 打开version_autogenerated.h,里面
只有一行话#define U_BOOT_VERSION "U-Boot 2010.06",它里面的内容是一个宏定义,宏定义的值
内容就是我们在Makefile中配置的uboot的版本号。
(4)验证方法:自己修改主Makefile中几个version有关的变量,然后重新编译uboot,然后
去看启动时uboot打印出来的版本信息。
注意这里的赋值是用=  变量obj要往后找
(5)
在Makefile中 如下两种写法等价
var=$(shell uname -m)
var=`uname -m`

HOSTARCH := $(shell uname -m | \
    sed -e s/i.86/i386/ \
        -e s/sun4u/sparc64/ \
        -e s/arm.*/arm/ \
        -e s/sa110/arm/ \
        -e s/ppc64/powerpc/ \
        -e s/ppc/powerpc/ \
        -e s/macppc/powerpc/)
执行命令uname -m的结果i686(主机CPU的体系架构类型),把结果送入管道
sed -e 表示后面跟的是一串命令脚本,而表达式s/abc/def表示要从标准输入中,
查找到内容为abc的,然后替换成def,其中,abc表达式可以使用.作为通配符。
最终结果HOSTARCH=i386
HOSTARCH主机CPU架构
HOSTOS主机操作系统
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
        sed -e 's/\(cygwin\).*/cygwin/')

shell uname -s      输出Linux
tr '[:upper:]' '[:lower:]'   转换成小写

(6)HOSTARCH和HOSTOS这两个环境变量是主机的CPU架构和主机的操作系统,得出后保存
备用,后面自然会用到。用export导出就是环境变量。
export    HOSTARCH HOSTOS SHELL
24.静默编译
(1)平时默认编译时命令行会打印出来很多编译信息。
但是有时候我们不希望看到这些编译信息,就后台编译即可。这就叫静默编译。
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
(2)使用方法就是编译时make -s,-s 会作为MAKEFLAGS传给
Makefile,此时就走这个分支XECHO = :,于是实现了静默编译。
make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- -s25. 2种编译方法(原地编译和单独输出文件编译)
(1)编译复杂项目,Makefile提供2种编译管理方法。默认情况下是当前文件夹中
的.c文件,编译出来的.o文件会放在同一文件夹下。这种方式叫原地编译。
原地编译的好处就是处理起来简单。
(2)原地编译有一些坏处:第一,污染了源文件目录。第二的缺陷就是一套源代码只能
按照一种配置和编译方法进行处理,无法同时维护2个或2个以上的配置编译方式。
(3)为了解决以上2种缺陷,uboot支持单独输出文件夹方式的编译(Linux kernel也支持,而且
uboot的这种技术就是从Linux kernel学习来的)。基本思路就是在编译时另外指定一个输出目录,将
所有的编译生成的.o文件或生成的其他文件全部丢到那个输出目录下去。源代码目录不做任何污染,这样输出目录就
承载了本次配置编译的所有结果。
(4)具体用法:默认的就是原地编译。如果需要指定具体的输出目录编译则有2种方式来指定输出目录
第一种:make O=输出目录  make O=/tmp/build all
第二种:先导出环境变量  export BUILD_DIR=输出目录,然后再make  export BUILD_DIR=/tmp/build  
如果两个都指定了(既有BUILD_DIR环境变量存在,又有O=xx),则O=xx具有更高的优先级
先到osdrv目录下make OSDRV_CROSS=arm-hisiv300-linux CHIP=hi3518ev200 distclean清除编译文件及镜像
根目录下面的README
2892         make O=/tmp/build distclean
2893         make O=/tmp/build NAME_config
2894         make O=/tmp/build all
26.环境变量OBJTREE、SRCTREE、TOPDIR
(1)OBJTREE:编译出的.o文件存放的目录的根目录。在默认编译下,OBJTREE等于当前目录;
在O=xx编译下,OBJTREE就等于我们设置的那个输出目录。
(2)SRCTREE:源码目录,其实就是源代码的根目录,也就是当前目录。
总结:在默认编译下,OBJTREE和SRCTREE相等;在O=xx这种编译下OBJTREE和SRCTREE不相等。
Makefile中定义的这两个变量,其实就是为了记录编译后的.o文件往哪里放,就是为了实现O=xx的这种
编译方式的。
OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE        := $(CURDIR)
TOPDIR        := $(SRCTREE)
LNDIR        := $(OBJTREE)
export    TOPDIR SRCTREE OBJTREE
27. 环境变量MKCONFIG
Makefile中定义的一个变量(在这里定义,在后面使用),它的值就是我们源代码根目录下面的mkconfig。
mkconfig是一个脚本,这个脚本就是uboot配置阶段的配置脚本。
MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG

include $(obj)include/config.mk
(1)include/config.mk不是源代码自带的(你在没有编译过的源码目录下是找不到
这个文件的),要在配置过程(make hi3518ev200_config)中才会生成这个文件。
因此这个文件的值和我们配置过程有关,是由配置过程根据我们的配置自动生成的。
include $(obj)include/config.mk  (154)
执行这三条命令后
make O=./output/ distclean
make O=./output/ hi3518ev200_config
make O=./output/ all
到output下面的include目录查看 cat config.mk 
ARCH   = arm
CPU    = hi3518ev200
BOARD  = hi3518ev200
SOC    = hi3518ev200
这些内容是配置过程自动生成的
export    ARCH CPU BOARD VENDOR SOC   (155)
导出了这5个变量作为环境变量。所以这两行加起来其实就是为了当前makefile定义
了5个环境变量而已。之所以不直接给出这5个环境变量的值,是因为我们希望这5
个值可以被人很容易的、集中的配置的。
hi3518ev200_config: unconfig
    @$(MKCONFIG) $(@:_config=) arm hi3518ev200 hi3518ev200 NULL hi3518ev200   (3277)
这里就是在配置,调用的是MKCONFIG这个脚本,然后把这些arm hi3518ev200 hi3518ev200 NULL hi3518ev200
作为参数传进去。传进去的这5个参数就会在MKCONFIG这个脚本里面自动制作成include/config.mk这个文件。
因此需要修改$(obj)include/config.mk里面的值。就需要去(3277)这里的传参的参数值。

28. ARCH CROSS_COMPILE
(1)接下来有2个很重要的环境变量。一个是ARCH,上面导出的,值来自我们的配置过程,它的值会影响后面的
CROSS_COMPILE环境变量的值。ARCH的意义是定义当前编译的目标CPU的架构。
(2)CROSS_COMPILE是定义交叉编译工具链的前缀的。编译工具链指的不是一个,而是一个集合,不过这些工具的前缀
是相同的,如arm-hisiv300-linux-uclibcgnueabi
arm-hisiv300-linux-uclibcgnueabi-gcc
arm-hisiv300-linux-uclibcgnueabi-c++
定义这些前缀是为了在后面用(用前缀加上后缀来定义编译过程中用到的各种工具链中的工具)。
我们把前缀和后缀分开还有一个原因就是:在不同的CPU架构上的交叉编译工具链,只是前缀不一样,后缀都是一样的
arm-hisiv500-linux-uclibcgnueabi-gcc
arm-hisiv500-linux-uclibcgnueabi-c++
因此定义时把前缀和后缀分开,只需要在定义前缀时区分各种架构即可实现可移植性。
(3)实际运用时,我们可以在Makefile中去更改设置CROSS_COMPILE的值,也可以在编译时用make
CROSS_COMPILE=xxxx来设置,而且编译时传参的方法可以覆盖Makefile里面的设置。
CROSS_COMPILE是被ARCH所确定的,只要配置了ARCH=arm,那么我们就只能在ARM的那个分支去设置CROSS_COMPILE
的值,这个设置值只要能保证找到那个交叉编译工具链即可,不一定非得是全路径的,相对路径也可以。(如果
已经将工具链导出到环境变量并且设置了符号链接,这样CROSS_COMPILE=arm-linux就可以)。

29. include $(TOPDIR)/config.mk  (163)
把config.mk这个makefile包含进来
(1)编译工具定义
# Include the make variables (CC, etc...)     (config.mk  102行)
#
AS    = $(CROSS_COMPILE)as
LD    = $(CROSS_COMPILE)ld
CC    = $(CROSS_COMPILE)gcc
CPP    = $(CC) -E
AR    = $(CROSS_COMPILE)ar
NM    = $(CROSS_COMPILE)nm
LDR    = $(CROSS_COMPILE)ldr
STRIP    = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
RANLIB    = $(CROSS_COMPILE)RANLIB
(2)包含开发板配置项目
# Load generated board configuration           (config.mk  118行)
sinclude $(OBJTREE)/include/autoconf.mk
autoconf.mk文件不是源代码提供的,是配置过程自动生成的。
这个文件的作用就是用来指导整个uboot的编译过程。这个文件的内容其实就是很多CONFIG_开头的宏(可以理解
为变量),这些宏/变量会影响我们的uboot编译过程的走向(原理就是条件编译)。在uboot代码中有很多地方使用
条件编译进行编写,这个条件编译是用来实现可移植性的。(可以说uboot的源代码在很大程度上来说是拼凑起来的
同一个代码包含了各种不同开发板的适用代码,用条件编译进行区别)
这个文件不是凭空产生的,配置过程也是需要原材料产生这个文件的。原材料在源代码目录的
Z:\u-boot-2010.06\include\configs\hi3518ev200.h
这个h头文件里面全部是宏定义,这些宏定义就是我们对当前开发板的移植。每一个开发板的移植都对应这个目录下的
一个头文件。这头文件的配置项就是我们开发板的配置项。有多少个开发板就需要多少个头文件。

30.  ifndef LDSCRIPT     (config.mk  155行)
#LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug
ifeq ($(CONFIG_NAND_U_BOOT),y)
指的是$(OBJTREE)/include/autoconf.mk里面是否有定义CONFIG_NAND_U_BOOT
(1)如果定义了CONFIG_NAND_U_BOOT宏,则链接脚本叫u-boot-nand.lds,如果未定义这个宏则链接脚本叫u-boot.lds
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
endif
endif
文件u-boot.lds是整个uboot编译链接的时候用的编译链接脚本
(2)TEXT_BASE      (config.mk 169)
ifneq ($(TEXT_BASE),)
CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
endif
TEXT_BASE是将来我们整个uboot链接时指定的链接地址
ifdef    BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk    # include board specific rules
endif
Makefile中在配置hi3518ev200开发板时,在u-boot-2010.06\board\hi3518ev200目录下生成一个文件config.mk,是一个Makefile文件,将来在某个Makefile中会去调用它。其中
的内容就是TEXT_BASE = 0x80800000,相当于定义了一个变量。

31. all:        $(ALL)   (291)
(1)出现了整个主Makefile中的第一个目标all(也就是默认目标,我们直接在uboot根目录下make其实就是等于
make all,就等于make这个目标)
(2)目标中有一些比较重要的。譬如u-boot是编译链接生成的elf格式的可执行文件。
(3)目标unconfig
unconfig:
    @rm -f $(obj)include/config.h $(obj)include/config.mk \
        $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \
        $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep
字面意思就是未配置。这个符号用来作为我们各个开发板配置目标的依赖。目标是当我们已经配置过一个开发板后再次去配置时
还可以配置。

32.uboot配置过程详解
(1)make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- hi3518ev200_config

hi3518ev200_config: unconfig
    @$(MKCONFIG) $(@:_config=) arm hi3518ev200 hi3518ev200 NULL hi3518ev200

$(@:_config=)解释:$与@结合表示目标,这里就是hi3518ev200_config,后面的冒号:表示对他进行加工
怎么加工,表示目标里面的_config这一部分用等号后面的内容来替换,等号后面为空。
即把hi3518ev200_config里的_config部分用空替换,得到:hi3518ev200作为第一个参数$1
所以:
$1:hi3518ev200
$2:arm
$3:hi3518ev200
$4:hi3518ev200
$5:NULL
$6:hi3518ev200
传了6个参数进来,所以$#=6

33. 进入顶层目录下面的mkconfig这个脚本去分析
(1)
while [ $# -gt 0 ] ; do      传参参数个数为$#=6进入循环
    case "$1" in             第一个参数$1为hi3518ev200,只能与通配符*匹配上,直接跳出while,相当于啥事没干
    --) shift ; break ;;
    -a) shift ; APPEND=yes ;;
    -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
    -t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
    *)  break ;;
    esac
done
(2)
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"      这是简化的if语句,
由BOARD_NAME=""    # Name to print in make output可知,BOARD_NAME为空,${BOARD_NAME}为faul,
BOARD_NAME="$1" 被执行,即BOARD_NAME=hi3518ev200

(3)
[ $# -lt 4 ] && exit 1    当[ $# -lt 4 ]为true时,才会去执行exit 1 (mkconfig脚本返回1,执行错误,不执行了,正确的时候都是返回0)
[ $# -gt 6 ] && exit 1
这两行的意思就是,给mkconfig传参只能是4,5,6三种情况,大于6或者小于4都不行
(4)
echo "Configuring for ${BOARD_NAME} board..."  这一行在配置成功的时候打印出来。
(5)创建符号链接
#
# Create link to architecture specific headers
#
if [ "$SRCTREE" != "$OBJTREE" ] ; then         如果使用make O=这种情况进进入
    mkdir -p ${OBJTREE}/include
    mkdir -p ${OBJTREE}/include2
    cd ${OBJTREE}/include2
    rm -f asm
    ln -s ${SRCTREE}/arch/$2/include/asm asm
    LNPREFIX=${SRCTREE}/arch/$2/include/asm/
    cd ../include
    rm -f asm
    ln -s ${SRCTREE}/arch/$2/include/asm asm    第一个软链接asm -> ../arch/arm/include/asm/
else                                            默认编译进入
    cd ./include
    rm -f asm
    ln -s ../arch/$2/include/asm asm            
fi

rm -f asm/arch         就是删除/arch/arm/include/asm/arch这个目录

if [ -z "$6" -o "$6" = "NULL" ] ; then      解释:只要-z "$6"(-z的意思是判断字符是否为空)或者"$6" = "NULL"成立即为true -o为或的意思
    ln -s ${LNPREFIX}arch-$3 asm/arch       这里$3=hi3518ev200  这个目录arch-hi3518ev200在\arch\arm\include\asm\arch-hi3518ev200
else
    ln -s ${LNPREFIX}arch-$6 asm/arch       第二个软链接路径./arch/arm/include/asm/arch   
fi

if [ "$2" = "arm" ] ; then
    rm -f asm/proc
    ln -s ${LNPREFIX}proc-armv asm/proc     第三个软链接路径./arch/arm/include/asm/proc
fi
为什么要创建符号链接?
这些符号链接文件的存在就是整个配置过程的核心,这些符号链接文件(文件夹)
的主要作用是给头文件包含过程提供指向性链接,根本的目的是让uboot具有可移植性。
uboot可移植性的实现原理:在uboot中有很多彼此平行的代码,各自属于各自不同的架构
/cup/开发板,我们在具体到一个开发板的编译时用符号链接的方式提供一个具体的名字的文件夹
供编译时使用。这样就可以在配置的过程中通过不同的配置使用的文件,就可以正确的包含正确的文件。

总结:一共创建了3个符号链接。这3个符号链接将来在写代码过程中,头文件包含时非常有用。譬如一个头文件包含
可能是:#include<asm/xx.h>

(6)创建config.mk文件
a.创建include/config.mk文件(mkconfig 70行)
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk
echo "CPU    = $3" >> config.mk
echo "BOARD  = $4" >> config.mk

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk

b. 创建include/config.mk文件是为了让主Makefile在154行去包含
    include $(obj)include/config.mk  (154)

(7)创建config.h
# Create board specific header file
#
if [ "$APPEND" = "yes" ]    # Append to existing config file
then
    echo >> config.h     当执行make -a(APPEND = yes)时追加
else
    > config.h        # Create new config file   默认情况为创建
fi    
echo "/* Automatically generated - do not edit */" >>config.h

for i in ${TARGETS} ; do
    echo "#define CONFIG_MK_${i} 1" >>config.h ;
done
这个文件config.h在/include/下面,本来是没有的,是在配置过程中产生的

cat config.h
/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/hi3518ev200
#include <config_defaults.h>
#include <configs/hi3518ev200.h>  这个文件是我们移植3518ev200开发板时,对开发板的宏定义配置文件。这个文件使我们移植3518ev200时最主要的文件
#include <asm/config.h>

这个文件configs/hi3518ev200.h最终会被配置成autoconfig.mk,这个文件会被主Makefile引入,指导整个编译过程。
这里的这些宏定义会影响我们对uboot中大部分.c文件中一些条件编译的选择。从而实现最终的可移植性。

注意:uboot的整个配置过程,很多文件之间是有关联的(有时候这个文件是在那个文件中创建出来的;有时候这个文件
被那个文件包含进去;有时候这个文件是有那个文件的内容生成的决定的)
注意:uboot中配置和编译过程,所有的文件或者全局变量都是字符串形式的(不是指的C语言字符串的概念,指的是
字符组成的序列)。这意味着我们整个uboot的配置过程都是字符串匹配的,所以一定要仔细,注意大小写,要注意不要输错。
因为一旦错一个最后会出现一些莫名其妙的错误,很难排查,这个是uboot移植过程中新手来说很难的地方。

34. uboot的链接脚本
(1)uboot的链接脚本和我们之前裸机中的链接脚本没有本质区别,只是复杂度高一些,文件多一些,使用到的技巧多一些。
(2)uboot的链接脚本和之前裸机中的链接脚本
/arch/arm/cpu/hi3518ev200 u-boot.lds
_start 就是汇编起始的函数名,相当于C语言中的main。
ENTRY(_start)  用来指定程序入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个程序的第一
句指令。
(3)之前在裸机中告诉大家,指定程序的链接地址有2种方法:一种是在Makefile中的ld的flags用-Ttext 0x20000000
来指定;第二种是在链接脚本的SECTIONS开头用. = 0x20000000来指定。
两种都可以实现相同的效果。其实这两种技巧是可以共同配合使用的,也就是说既可以在链接脚本中指定也在ld flags中
用-Ttext来指定。两个都指定以后易-Ttext指定的为准。
(4)uboot的最终链接接起来地址就是在Makefile中用-Ttext来指定的,具体参考30.  ifndef LDSCRIPT     (config.mk  155行)
注意TEXT_BASE变量。TEXT_BASE最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
(5). = ALIGN(4);
四字节对齐,怕有些人传TEXT_BASE的值不是按四字节对齐传的。
(6)代码段要注意文件的排列顺序
    .text    :
    {
        __text_start = .;
        arch/arm/cpu/hi3518ev200/start.o (.text)
        arch/arm/cpu/hi3518ev200/lowlevel_init_svb.o (.text)
        drivers/ddr/ddr_training_impl.o (.text)
        drivers/ddr/ddr_training_ctl.o (.text)
        drivers/ddr/ddr_training_boot.o (.text)
        drivers/ddr/ddr_training_custom.o (.text)
        __init_end = .;
        ASSERT(((__init_end - __text_start) < 0x4000), "init sections too big!");
        *(.text)
    }
指出名字的按照顺序往前站,没有指出名字的用*(.text)代替,往后站。
这些文件的先后顺序将来会影响.o文件在我们的最终的uboot.bin里面的排列顺序。
因为uboot启动的时候分成两部分,前16kB和16KB之后的部分。启动的时候前16KB作为BLE被加载进来。
BLE负责初始化DDR,iNAND,要能够把剩下的部分能够读到DDR里面去。然后再去启动它。
所以你要保证初始化DDR,初始化iNAND做重定位等这些代码必须要在前16KB,前面单独写出来的代码就是在
前16KB的。

指出必须放在前面部分的那些文件就是那些必须安排在前16KB内的文件,这些文件中的函数在前16KB会被调用。
在后面第二部分(16KB之后)中调用的程序,前后顺序就无所谓了。
(7)链接脚本中除了.text .data .rodata  .bss段等编译工具自带的段之外,编译工具还允许我们自定义段。
譬如uboot中的 .u_boot_cmd段就是自定义段。自定义很重要。


35. start.S引入
uboot分为两个阶段:一、汇编阶段。二、C语言阶段。
汇编阶段是在内部SRAM中运行的阶段。
C语言阶段在DDR中运行。
(1)在C语言中整个项目的入口就是MAIN函数(这是C语言规定的),所以譬如说一个有10000个.c文件的项目
,第一个要分析的文件就是包含了main函数的那个文件。
(2)在uboot中因为有汇编阶段参与,因此不能直接找main.c。整个程序的入口取决于链接脚本中ENTRY声明的
地方。ENTRY(_start)因此_start符号所在的文件就是整个程序的起始文件,_start所在代码就是整个程序的
起始代码。
通过SI查找_start
z:\hi3518e_sdk_v1.0.3.0\osdrv\opensource\uboot\u-boot-2010.06\arch\arm\cpu\hi3518ev200
.globl _start       把_start声明成全局,在别的地方来调用
_start: b    reset   这里就是整个uboot的入口,_start是一个标号,是紧接后面代码b    reset所对应的地址
    ldr    pc, _undefined_instruction
    ldr    pc, _software_interrupt
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq
(3)以上,找到了start.S文件,下面我们就从start.S文件开始分析uboot第一阶段
(4)在SI中,如果我们知道我们找的文件的名字,但是我们不知道他在哪个目录下,我们要怎样找到打开这个
文件?即使你不记得文件的全名只是大概记得名字,也能帮助你找到你要找的文件。

36. 不简单的头文件包含
(1)#include
#include <config.h>
#include <version.h>

<config.h> config.h是在include目录下的,这个文件不是源代码中本身存在的文件,而是配置过程中自动生成的文件
。这个文件的内容其实是包含了
/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/hi3518ev200
#include <config_defaults.h>
#include <configs/hi3518ev200.h>
#include <asm/config.h>
(2)经过分析后,发现start.S中包含的第一个头文件就是:include/configs/hi3518ev200.h
这个文件是整个uboot移植时的配置文件。这里面是好多宏。
start.S程序中多处提到是否定义了某个宏指的就是这个文件include/configs/hi3518ev200.h是否定义.
因此这个头文件包含将include/configs/hi3518ev200.h文件和start.S文件关联了起来。
因此之后在分析start.S文件时,主要要考虑的就是x210_sd.h文件。
(3)#include 
<version.h>
include/version.h中包含了include/version_autogenerated.h,这个头文件就是配置过程中自动生成的。
里面就一行内容:#define U_BOOT_VERSION "U-Boot 2010.06"
这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本信息来自Makefile中的配置值。
这个宏在程序中会被调用,在uboot启动过程中会打印出uboot的版本号,那个版本号信息就是从这里来的。

如果Makefile中的值有修改,
VERSION = 2010
PATCHLEVEL = 06
SUBLEVEL =
EXTRAVERSION =   Edison   //添加自己的名字
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
make distclean后,重新经过配置编译后,version_autogenerated.h中的值也会跟着改变。
make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- hi3518ev200_config   配置
make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux-  编译
include/version_autogenerated.h中的值变成#define U_BOOT_VERSION "U-Boot 2010.06Edison"

(4)#include
<asm/proc/domain.h>
asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的,去include目录下去找根本找不到asm目录
include/asm -> ../arch/arm/include/asm
include/asm目录是配置时创建的一个符号链接,实际指向的是就是../arch/arm/include/asm
proc -> proc-armv
include/asm/proc目录指向的是与它同级目录下的proc-armv的目录
proc目录也是编译后才生成的,原生是不存在的。
所以这个包含<asm/proc/domain.h>实际就等价于/arch/arm/include/asm/proc-armv/domain.h
从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,
因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)
思考:为什么start.S不直接包含/arch/arm/include/asm/proc-armv/domain.h,而要用asm/proc/domain.h
这样的设计主要是为了可移植性。因为如果直接包含,则start.S文件和CPU架构(硬件)有关了。
可移植性就差,譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。
我们用了符号链接之后,则start.S源代码不用改,只要在具体具体的硬件移植时配置不同,创建的符号链接指向
的不同,即让asm指向自己需要的对应硬件目录即可,则可以具有可移植性,源代码就不用动。

(5)启动代码的16字节头部
裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。
(mkv210image.c中就是为了计算这个校验头)。我们以前做裸机程序时根本没有考虑这16字节校验头,
因为1.如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note);
2.如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。
uboot这里start.S中在开头位置放了16字节的填充占位(根本就不考虑通过usb下载的这种方式),
这个占位的16字节只是保证正式的image的头部
确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算检验和然后重新填充的。
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
    .word 0x2000    //.word 是GNU汇编里面的一个伪指令,相当于是一个int类型的变量,占4个字节,值是0x2000,它的地址就是这个位置的地址。
    .word 0x0       //相当于我们在这里定义了一个int类型的数组,数组里面有四个元素,第一个元素的值是0x2000
    .word 0x0       //一共加起来16个字节
    .word 0x0
#endif
这16个字节的用来干什么的?
程序最开始的校验头
去哪里计算校验和填充,在uboot的根目录里面有一个目录sd_fusing,里面有一个C110-EVT1-mkbl1.c 
这个文件里面的内容应该和
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
    .word 0x2000    //.word 是GNU汇编里面的一个伪指令,相当于是一个int类型的变量,占4个字节,值是0x2000,它的地址就是这个位置的地址。
    .word 0x0       //相当于我们在这里定义了一个int类型的数组,数组里面有四个元素,第一个元素的值是0x2000
    .word 0x0       //一共加起来16个字节
    .word 0x0
#endif
相对应,否则编译出来的uboot不能启动。

(6)异常向量表的构建(一级中断向量表)
.globl _start
_start: b    reset          //复位异常,reset是一个函数,
    ldr    pc, _undefined_instruction  // _undefined_instruction是一个函数
    ldr    pc, _software_interrupt //
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq
每一个CPU都会有一个异常向量表,即一级中断向量表,每一就对应将来发生异常的时候跳过来做相应的动作
函数就在下面定义
_undefined_instruction: .word undefined_instruction   //undefined_instruction就是函数_undefined_instruction对应的值
_software_interrupt:    .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:        .word data_abort
_not_used:        .word not_used
_irq:            .word irq
_fiq:            .word fiq
下面有处理未定义指令异常undefined_instruction详细的处理过程
undefined_instruction:
    get_bad_stack
    bad_save_user_regs
    bl    do_undefined_instruction
复位异常详细的处理方法,系统复位的真实代码,系统的每一次启动其实就是一次复位
复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,
因此reset符号处才是真正有意义的代码开始的地方。
/*
 * the actual reset code
 */
 
reset:
    /*
     * set the cpu to SVC32 mode
     */
    mrs    r0, cpsr
    bic    r0, r0, #0x1f
    orr    r0, r0, #0xd3
    msr    cpsr,r0    

异常向量表是硬件决定的,软件只是参照硬件的设计来实现它
异常向量表中每一种异常都应该被处理,否则真遇到了这种异常就跑飞了。
但是我们在uboot中并未非常细致的处理各种异常。因为uboot运行的时间就比较短,要是真出现了异常,硬件重启
就OK,但是操作系统就不能这么搞,操作系统必须非常细致的处理各种异常。所以说uboot不是很完善。

37. 有意思的deadbeef
(1).balignl 16,0xdeadbeef. 这一句指让当前地址对齐排布,如果当前地址不对齐则自动向后走地址
直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。
(2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制全是abcdef中
的字母,而且这8个字母刚好组成了英文单词deadbeef这两个单词,字母意思是坏牛肉。
(3)告诉读代码的人填充的那一段可以当垃圾扔掉,填充的那一段没有意义。只是老外用文字开的一个玩笑而已。
研究这个纯属无聊。
(4)这里的0xdeadbeef和0x00000000没啥两样。
(5)为什么要对齐访问,有时候是效率的要求,有时候是硬件的特殊要求。

38.  TEXT_BASE
_TEXT_BASE:              //标号
    .word    TEXT_BASE   //.word是定义4个字节的变量,TEXT_BASE是变量的值
//有点类似于定义了一个指针,这个指针的变量的名字是_TEXT_BASE,指针变量的值是TEXT_BASE,.word表示我们这个指针是一个int类型的或者unsigned int 或者是4字节类型的。
//定义了这个东西之后呢,将来我们可以通过ldr去加载_TEXT_BASE这个符号,从而直接加载TEXT_BASE这个符号的值,只是汇编的一种技巧。


(1)TEXT_BASE完全是引用的,没有定义它的,根本搜不到
(2)这个TEXT_BASE就是分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们链接时指定的
uboot的链接地址。(值就是0x80800000)
(3)源代码中和配置Makefile中很多变量是可以互相运送的,简单来说有些符号的值可以从Makefile中
传递到源代码中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值