uboot_uboot 裁剪 - CSDN
精华内容
参与话题
  • 一.linux开发之uboot移植(一)——初识uboot

    万次阅读 多人点赞 2018-01-22 13:11:04
    一、uboot简介 U-Boot,全称 Universal Boot Loader,是遵循GPL条款的从FADSROM、8xxROM、PPCBOOT逐步发展演化而来的 开放源码项目。 - 在操作系统方面 ,U-Boot不仅支持 - 嵌入式Linux系统的引导,它还支持...

    参考博文: http://blog.51cto.com/9291927/1791237

    一、uboot简介

    U-Boot,全称 Universal Boot Loader,是遵循GPL条款的从FADSROM、8xxROM、PPCBOOT逐步发展演化而来的 开放源码项目。
    - 在操作系统方面 ,U-Boot不仅支持
    - 嵌入式Linux系统的引导,它还支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android嵌入式操作系统。目前支持的目标操作系统是OpenBSD, NetBSD, FreeBSD,4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS, android。
    - 在CPU架构方面 ,U-Boot除了支持PowerPC系列的处理器外,还能支持MIPS、 x86、ARM、NIOS、XScale等诸多常用系列的处理器。
    - uboot主要作用 是用来启动操作系统内核

    * *

    uboot什么时候开始运行,什么时候结束运行?

      1.uboot本质上是一个裸机程序(不是操作系统),一旦uboot开始SoC就会单纯运行uboot(意思就是uboot运行的时候别的程序是不可能同时运行的),一旦uboot结束运行则无法再回到uboot(所以uboot启动了内核后uboot自己本身就死了,要想再次看到      uboot界面只能重启系统。重启并不是复活了刚才的uboot,重启只是uboot的另一生)
    
      2. **uboot的入口就是开机自动启动,uboot的唯一出口就是启动内核** 。uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了
    

    二、uboot的工作模式

    * U-Boot的工作模式有启动加载模式和下载模式。*

    1、启动加载模式

    启动加载模式是Bootloader的正常工作模式,嵌入式产品发布时,Bootloader必须工作在这种模式下,Bootloader将 [嵌入式操作系统](file:///h)从FLASH中加载到SDRAM中运行,整个过程是自动的。
    

    2、下载模式

    下载模式就是Bootloader通过某些通信手段将 内核映像或 根文件系统映像等从PC机中下载到 目标板的FLASH中。用户可以利用Bootloader提供的一些命令接口来完成自己想要的操作。开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机(Host)下载文件(比如内核映像、文件系统映像),将它们直接放在内存运行或是烧入Flash类固态存储设备中。

    板子与主机间传输文件时,可以使用串口的xmodem/ymodem/zmodem协议,还可以使用网络通过tftp、nfs协议来传输,以及USB下载等方法。

    一般来说,嵌入式开发人员采用下载模式进行开发嵌入式系统。通常采用交叉网线将PC与目标开发板连接,通过TFTP服务器下载内核,用NFS服务器挂载文件系统。

    三、uboot的常用命令

    1** 、获取命令**

    命令:help 或 ?

    功能:查看当前U-boot版本中支持的所有命令。
    

    2、环境变量命令

    环境变量有2份,一份在Flash中,另一份在DDR中。uboot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,然后使用过程中都是用DDR中这一份

    bootdelay 执行自动启动(bootcmd中的命令)的等候秒数
    baudrate 串口控制台的波特率
    netmask 以太网的网络掩码
    ethaddr 以太网的MAC地址
    bootfile 默认的下载文件名
    bootargs 传递给Linux内核的启动参数
    bootcmd 自动启动时执行命令
    serverip TFTP服务器端的IP地址
    ipaddr 本地的IP地址
    stdin 标准输入设备,一般是串口
    stdout 标准输出,一般是串口,也可是LCD(VGA)
    stderr 标准出错,一般是串口,也可是LCD(VGA)

    - 使用* print *命令可以打印出当前开发板的环境变量。
    - setenv envname value设置环境变量的值(设置后记得save保存

    (1)新建一个环境变量,使用set var value

    (2)更改一个环境变量,使用set var value

    (3)删除一个环境变量,使用set var

         (4)save将修改的环境变量保存到固态存储器中。
    
    • bootcmd 自动启动执行命令

    uboot开机后会自动倒计时,在倒计时结束前如果没有外部按键打断自动计时,uboot将自动执行bootcmd变量保存的命令。
    这里写图片描述
    意思是:将iNand的kernel分区读取到DDR内存的0x30008000地址处,然后使用bootm启动命令从内存0x30008000处去启动内核。

    (1)可以将bootcmd设置为:set bootcmd print

    然后save保存;重启则会看到启动倒数后自动执行print命令打印出环境变量

    这里写图片描述
    (2) 再还原为内核启动命令:

    set bootcmd 'movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000'

    ( 中间有分号记得必须得加'' )

    • uboot给kernel传参:bootargs(内核移植中必定使用)

    (1)linux内核启动时可以接收uboot给他传递的启动参数,这些启动参数是uboot和内核约定好的形式、内容,linux内核在这些启动参数的指导下完成启动过程。

    (2)我们在uboot的环境变量中设置bootargs,然后bootm命令启动内核时会自动将bootargs传给内核。
    这里写图片描述
    意义解释:

    console=ttySAC2,115200 控制台使用串口2,波特率115200.

    root=/dev/mmcblk0p2 rw 根文件系统在SD卡端口0设备(iNand)第2分区,根文件系统是可读可写的

    init=/linuxrc linux的进程1(init进程)的路径

    rootfstype=ext3 根文件系统的类型是ext3

    3、网络命令

    . uboot可以通过网络来传输文件到开发板,直接用交叉网线连接开发板和电脑,也可以用普通直连网线连接路由器。

    ping ip
    
    • 网络命令搭建开发板uboot和虚拟机ubuntu互相ping通记录在另一博课笔记中

      如果网络连通,就可以通过tftp、NFS挂载开发板

    4.tftp下载指令:tftp

    作用:使uboot为了部署内核就需要将内核镜像从主机中下载过来然后烧录到本地flash中去。

    将要下载的镜像文件放在服务器的下载目录中,然后开发板中使用uboot的tftp命令去下载即可。

    我的虚拟机搭建的时候设置的tftp下载目录是/tftpboot,将要被下载的镜像复制到这个目录下。

    具体参考另一博客tftp服务器的安装搭建及使用(保证已经可以ping通)

    5.nfs启动内核命令:nfs

    作用: nfs服务,通过它"挂载"制作好的根文件系统。

    主机开启nfs服务后,就可以像tftp一样传文件到开发板了,而且nfs还可以挂载根文件系统,这就是nfs的主要作用

    具体参考另一博客* nfs服务器的安装及使用*

    6.SD卡/iNand操作指令movi

    • movi的指令都是movi read和movi write一组的,

      movi read用来读取iNand到DDR上,movi write用来将DDR中的内容写入iNand中。理解这些指令时一定要注意涉及到的2个硬件:iNand和DDR内存

    • movi指令是一个命令集,有很多子命令,具体用法可以help ,这里说明怎么看

      例:movi read {u-boot | kernel} {addr}

    这个命令使用了一种通用型的描述方法来描述:movi 和 read外面没有任何标记说明每一次使用这个指令都是必选的;一对大括号{}括起来的部分必选1个,大括号中的竖线表是多选一。中括号[]表示可选参数(可以有也可以没有)

    譬如命令 movi read u-boot 0x30000000表示如下:

    意思就是把iNand中的u-boot分区读出到DDR的0x30000000起始的位置处。

    (uboot代码中将iNand分成了很多个分区,每个分区有地址范围和分区名,uboot程序操作中可以使用直接地址来操作iNand分区,也可以使用分区名来操作分区。);注意这里的0x30000000也可以直接写作30000000,意思是一样的( uboot的命令行中所有数字都被默认当作十六进制处理 ,不管你加不加0x都一样)。

    7.NandFlash操作指令nand
    这里写图片描述
    8.内存操作指令:mm、mw、md

    • * nm * 修改内存值 * ( *指定地址* ) *

      格式: nm [.b, .w, .l] address

    • * mm 修改内存值(地址自动加一)*

      格式: mm [.b, .w, .l] address

    • * md 显示内存值*

      格式: md [.b, .w, .l] address [# of objects]

    • mw 用指定的数据填充内存

      格式: mw [.b, .w, .l] address value [count]

    • * cp 内存的拷贝(包括内存与Nor Flash间的数据拷贝)*

      格式:cp [.b, .w, .l] source target count

    9.启动内核指令:bootm、go

    uboot命令行中调用这个指令就会启动内核(不管成功与否,所以这个指令是一条死路)。

    差别: bootm启动内核同时给内核传参,而go命令启动内核不传参。 bootm其实才是正宗的启动内核的命令,一般情况下都用这个 ;go命令本来不是专为启动内核设计的,go命令内部其实就是一个函数指针指向一个内存地址然后直接调用那个函数,go命令的实质就是PC直接跳转到一个内存地址去运行而已

    四.uboot中对Flash和DDR的管理

    uboot在Flash中的分区

    Flash分区如下: 功能:
    自由分区 待用空间(一般做根文件系统使用)
    rootfs 根文件系统文件
    kernel 内核文件
    var 环境变量
    uboot bootlater(必须在最前面)

    (1)各分区彼此相连,前面一个分区的结尾就是后一个分区的开头。

    (2)整个flash充分利用,从开头到结尾。

    (3)uboot必须在Flash开头,其他分区相对位置是可变的。

    (4)各分区的大小由系统移植工程师自己来定,一般定为合适大小(不能太小,太小了容易溢出;不能太大,太大了浪费空间)

    (5)分区在系统移植前确定好,在uboot中和kernel中使用同一个分区表。将来在系统部署时和系统代码中的分区方法也必须一样。

    因为Flash是掉电不丢失的,因此,在对Flash进行分区的时候要考虑到以后的使用条件。而DDR是掉电丢失的,因此,在系统的每个阶段都可以对它进行重新分区,例如在uboot阶段它有自己的分区管理,而在kernel启动起来之后,整个内存又将被kernel给接替过来,kernel将会对内存进行重新的分区和管理。

    综上:DDR要根据具体使用情况对其进行分区管理,注意内存不要重叠。

    展开全文
  • 什么是ubootuboot有什么用?

    千次阅读 2019-10-17 09:11:19
    一、为什么要有uboot 1.1、计算机系统的主要部件        (1)计算机系统就是以CPU为核心来运行的系统。典型的计算机系统有:PC机(台式机+笔记本)、嵌入式设备(手机、平板电脑...

    一、为什么要有uboot


    1.1、计算机系统的主要部件

           (1)计算机系统就是以CPU为核心来运行的系统。典型的计算机系统有:PC机(台式机+笔记本)、嵌入式设备(手机、平板电脑、游戏机)、单片机(家用电器像电饭锅、空调)
           (2)计算机系统的组成部件非常多,不同的计算机系统组成部件也不同。但是所有的计算机系统运行时需要的主要核心部件都是3个东西:
    CPU + 外部存储器(Flash/硬盘) + 内部存储器(DDR SDRAM/SDRAM/SRAM)

    1.2、PC机的启动过程

           (1)部署:典型的PC机的BIOS程序部署在PC机主板上(随主板出厂时已经预制了),操作系统部署在硬盘上,内存在掉电时无作用,CPU在掉电时不工作。
           (2)启动过程:PC上电后先执行BIOS程序(实际上PC的BIOS就是NorFlash),BIOS程序负责初始化DDR内存,负责初始化硬盘,然后从硬盘上将OS镜像读取到DDR中,然后跳转到DDR中去执行OS直到启动(OS启动后BIOS就无用了)

    1.3、典型嵌入式linux系统启动过程
           (1)典型嵌入式系统的部署:uboot程序部署在Flash(能作为启动设备的Flash)上、OS部署在FLash(嵌入式系统中用Flash代替了硬盘)上、内存在掉电时无作用,CPU在掉电时不工作。
           (2)启动过程:嵌入式系统上电后先执行uboot、然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS(OS启动后uboot就无用了)

    总结:嵌入式系统和PC机的启动过程几乎没有两样,只是BIOS成了uboot,硬盘成了Flash。

    1.4、android系统启动过程
           (1)Android系统的启动和Linux系统(前面讲的典型的嵌入式系统启动)几乎一样。几乎一样意思就是前面完全一样,只是在内核启动后加载根文件系统后不同了。
           (2)可以认为启动分为2个阶段:第一个阶段是uboot到OS启动;第二个阶段是OS启动后到rootfs加载到命令行执行;现在我们主要研究第一个阶段,android的启动和linux的差别在第二阶段。

    1.5、总结:uboot到底是干嘛的        (1)uboot主要作用是用来启动操作系统内核。
           (2)uboot还要负责部署整个计算机系统。
           (3)uboot中还有操作Flash等板子上硬盘的驱动。
           (4)uboot还得提供一个命令行界面供人来操作。

    二、为什么是uboot

    2.1、uboot从哪里来的?

           (1)uboot是SourceForge上的开源项目
           (2)uboot项目的作者:一个德国人最早发起的项目
           (3)uboot就是由一个人发起,然后由整个网络上所有感兴趣的人共同维护发展而来的一个bootloader。

    2.2、uboot的发展历程

           (1)自己使用的小开源项目。
           (2)被更多人认可使用
           (3)被SoC厂商默认支持。

    总结:uboot经过多年发展,已经成为事实上的业内bootloader标准。现在大部分的嵌入式设备都会默认使用uboot来做为bootloader。

    2.3、uboot的版本号问题

           (1)早期的uboot的版本号类似于这样:uboot1.3.4。后来版本号便成了类似于uboot-2010.06。
           (2)uboot的核心部分几乎没怎么变化,越新的版本支持的开发板越多而已,对于一个老版本的芯片来说,新旧版本的uboot并没有差异。

    2.4、uboot的可移植性的正确理解

           (1)uboot就是universal bootloader(通用的启动代码),通用的意思就是在各种地方都可以用。所以说uboot具有可移植性。
           (2)uboot具有可移植性并不是说uboot在哪个开发板都可以随便用,而是说uboot具有在源代码级别的移植能力,可以针对多个开发板进行移植,移植后就可以在这个开发板上使用了。

    三、uboot必须解决哪些问题

    3.1、自身可开机直接启动

           (1)一般的SoC都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等·····uboot要能够开机启动,必须根据具体的SoC的启动设计来设计uboot。
           (2)uboot必须进行和硬件相对应的代码级别的更改和移植,才能够保证可以从相应的启动介质启动。uboot中第一阶段的start.S文件中具体处理了这一块。

    3.2、能够引导操作系统内核启动并给内核传参

           (1)uboot的终极目标就是启动内核。
           (2)linux内核在设计的时候,设计为可以被传参。也就是说我们可以在uboot中事先给linux内核准备一些启动参数放在内存中特定位置然后传给内核,内核启动后会到这个特定位置去取uboot传给他的参数,然后在内核中解析这些参数,这些参数将被用来指导linux内核的启动过程。

    3.3、能提供系统部署功能

           (1)uboot必须能够被人借助而完成整个系统(包括uboot、kernel、rootfs等的镜像)在Flash上的烧录下载工作。
           (2)裸机教程中刷机(ARM裸机第三部分)就是利用uboot中的fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。

    3.4能进行soc级和板级硬件管理

           (1)uboot中实现了一部分硬件的控制能力(uboot中初始化了一部分硬件),因为uboot为了完成一些任务必须让这些硬件工作。譬如uboot要实现刷机必须能驱动iNand,譬如uboot要在刷机时LCD上显示进度条就必须能驱动LCD,譬如uboot能够通过串口提供操作界面就必须驱动串口。譬如uboot要实现网络功能就必须驱动网卡芯片。
           (2)SoC级(譬如串口)就是SoC内部外设,板级就是SoC外面开发板上面的硬件(譬如网卡、iNand)

    3.5、uboot的“生命周期”

           (1)uboot的生命周期就是指:uboot什么时候开始运行,什么时候结束运行。
           (2)uboot本质上是一个裸机程序(不是操作系统),一旦uboot开始SoC就会单纯运行uboot(意思就是uboot运行的时候别的程序是不可能同时运行的),一旦uboot结束运行则无法再回到uboot(所以uboot启动了内核后uboot自己本身就死了,要想再次看到uboot界面只能重启系统。重启并不是复活了刚才的uboot,重启只是uboot的另一生)
           (3)uboot的入口和出口。uboot的入口就是开机自动启动,uboot的唯一出口就是启动内核。uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了。
    总结:一切都是为了启动内核

    四、uboot的工作方式

    4.1、从裸机程序镜像uboot.bin说起

           (1)uboot的本质就是一个裸机程序,和我们裸机全集中写的那些裸机程序xx.bin并没有本质区别。如果非说要有区别,那就是:我们写的大部分小于16KB,而uboot大于16KB(一般uboot在180k-400k之间)
           (2)uboot本身是一个开源项目,由若干个.c文件和.h文件组成,配置编译之后会生成一个uboot.bin,这就是uboot这个裸机程序的镜像文件。然后这个镜像文件被合理的烧录到启动介质中拿给SoC去启动。也就是说uboot在没有运行时表现为uboot.bin,一般躺在启动介质中。
           (3)uboot运行时会被加载到内存中然后一条指令一条指令的拿给CPU去运行。

    4.2、uboot的命令式shell界面

           (1)普通的裸机程序运行起来就直接执行了,执行时效果和代码有关。
           (2)有些程序需要和人进行交互,于是乎程序中就实现了一个shell(shell就是提供人机交互的一个界面,回想ARM裸机全集第十六部分),uboot就实现了一个shell。
    注意:shell并不是操作系统,和操作系统一点关系都没有。linux中打开一个终端后就得到了一个shell,可以输入命令回车执行。uboot中的shell工作方式和linux中的终端shell非常像(其实几乎是一样的,只是命令集不一样。譬如linux中可以ls,uboot中ls就不识别)

    4.3、掌握uboot使用的2个关键点:命令和环境变量

           (1)uboot启动后大部分时间和工作都是在shell下完成的(譬如uboot要部署系统要在shell下输命令、要设置环境变量也得在命令行地下,要启动内核也要在命令行底下敲命令)
           (2)命令就是uboot的shell中可以识别的各种命令。uboot中有几十个命令,其中有一些常用另一些不常用(我们还可以自己给uboot添加命令),后面会用几节课时间来依次学习uboot中常用命令。
           (3)uboot的环境变量和操作系统的环境变量工作原理和方式几乎完全相同。uboot在设计时借助了操作系统的设计理念(命令行工作方式借鉴了linux终端命令行,环境变量借鉴了操作系统的环境变量,uboot的驱动管理几乎完全照抄了linux的驱动框架)。
           (4)环境变量可以被认为是系统的全局变量,环境变量名都是系统内置的(认识就认识,不认识就不认识,这部分是系统自带的默认的环境变量,譬如PATH;但是也有一部分环境变量是自己添加的,自己添加的系统就不认识但是我们自己认识)。系统或者我们自己的程序在运行时可以通过读取环境变量来指导程序的运行。这样设计的好处就是灵活,譬如我们要让一个程序更改运行方法,不用去重新修改程序代码再重新编译运行,而只要修改相应的环境变量就可以了。
    (5)环境变量就是运行时的配置属性。

    展开全文
  • 史上最详细最全面的uboot启动过程分析,看完之后能对UBOOT有个全面的了解。绝对独家 史上最好的UBOOT分析教程。
  • 终于到了最后的函数了 static int run_main_loop(void) { #ifdef CONFIG_SANDBOX /* 没定义 */ sandbox_main_loop_init(); #endif /* main_loop() can return to retry autoboot, if so just run it again */ ...

    终于到了最后的函数了

    static int run_main_loop(void)
    {
    #ifdef CONFIG_SANDBOX    /* 没定义 */
    	sandbox_main_loop_init();
    #endif
    	/* main_loop() can return to retry autoboot, if so just run it again */
    	for (;;)
    		main_loop();
    	return 0;
    }
    

    1.1、main_loop

    void main_loop(void)
    {
    	const char *s;
    
          /* bootstage_mark_name函数调用了show_boot_progress,利用它显示启动进程(progress),
     7     此处为空函数 */
    	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
    
    #ifdef CONFIG_VERSION_VARIABLE            /* 没定义 */
    	setenv("ver", version_string);  /* set version variable */
    #endif /* CONFIG_VERSION_VARIABLE */
    
        /* cli_init用来初始化hush shell使用的一些变量。 */
    	cli_init();
    
        /* 函数从环境变量中获取"preboot"的定义,*/
        /* 该变量包含了一些预启动命令,一般环境变量中不包含该项配置。 */
    	run_preboot_environment_command();
    
    #if defined(CONFIG_UPDATE_TFTP)        /* 网络相关的没定义 */
    	update_tftp(0UL, NULL, NULL);
    #endif /* CONFIG_UPDATE_TFTP */
    
       /* bootdelay_process从环境变量中取出"bootdelay"和"bootcmd"的配置值 */
       /* 将取出的"bootdelay"配置值转换成整数,赋值给全局变量stored_bootdelay */
       /* 最后返回"bootcmd"的配置值 */
       /* bootdelay为u-boot的启动延时计数值,计数期间内如无用户按键输入干预,那么将执行"bootcmd"配置中的命令 */
    	s = bootdelay_process();    
    	if (cli_process_fdt(&s))    /* 环境变量里面有bootcmd,fdt如果有,fdt的会覆盖掉env里面的 */
    		cli_secure_boot_cmd(s);
        
         /* autoboot_command,倒数计时实现,计时到-1前,没按键会指行bootcmd *
    	autoboot_command(s);
    
        /* 进入 uboot 命令行中 */
    	cli_loop();
    	panic("No CLI available");
    }
    

    1.1.1、cli_init

    void cli_init(void)
    {
    #ifdef CONFIG_HUSH_PARSER         /* 定义了 */
    	u_boot_hush_start();
    #endif
    
    #if defined(CONFIG_HUSH_INIT_VAR)    /* 没定义 */
    	hush_init_var();
    #endif
    }
    
    /* 初始化哈希表用到的参数 */
    int u_boot_hush_start(void)
    {
    	if (top_vars == NULL) {
    		top_vars = malloc(sizeof(struct variables));
    		top_vars->name = "HUSH_VERSION";
    		top_vars->value = "0.01";
    		top_vars->next = NULL;
    		top_vars->flg_export = 0;
    		top_vars->flg_read_only = 1;
    #ifdef CONFIG_NEEDS_MANUAL_RELOC    /* 没定义 */
    		u_boot_hush_reloc();
    #endif
    	}
    	return 0;
    }

    1.1.2、run_preboot_environment_command

    
    static void run_preboot_environment_command(void)
    {
    #ifdef CONFIG_PREBOOT
    	char *p;
    
    	p = getenv("preboot");
    	if (p != NULL) {
    # ifdef CONFIG_AUTOBOOT_KEYED
    		int prev = disable_ctrlc(1);	/* disable Control C checking */
    # endif
    
    		run_command_list(p, -1, 0);
    
    # ifdef CONFIG_AUTOBOOT_KEYED
    		disable_ctrlc(prev);	/* restore Control C checking */
    # endif
    	}
    #endif /* CONFIG_PREBOOT */
    }

    1.1.2、bootdelay_process

    默认是没配置CONFIG_AUTOBOOT

    meke menuconfig配置添加这个宏,设置倒数时间,会出现开机倒数计时功能。

    
    const char *bootdelay_process(void)
    {
    	char *s;
    	int bootdelay;
    #ifdef CONFIG_BOOTCOUNT_LIMIT            /* 没定义 */
    	unsigned long bootcount = 0;
    	unsigned long bootlimit = 0;
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
    #ifdef CONFIG_BOOTCOUNT_LIMIT         /* 没定义 */
    	bootcount = bootcount_load();
    	bootcount++;
    	bootcount_store(bootcount);
    	setenv_ulong("bootcount", bootcount);
    	bootlimit = getenv_ulong("bootlimit", 10, 0);
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
    	s = getenv("bootdelay");            /* 这个是取得环境变量里的 */
    	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
    
    #ifdef CONFIG_OF_CONTROL    /* 得到刚才配置的环境变量里的时间,并转化为整数,或覆盖前面的 */
    	bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
    			bootdelay);
    #endif
    
    	debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
    
    #if defined(CONFIG_MENU_SHOW)        /* 没定义 */
    	bootdelay = menu_show(bootdelay);
    #endif
    	bootretry_init_cmd_timeout();
    
    #ifdef CONFIG_POST                /* 没定义 */
    	if (gd->flags & GD_FLG_POSTFAIL) {
    		s = getenv("failbootcmd");
    	} else
    #endif /* CONFIG_POST */
    #ifdef CONFIG_BOOTCOUNT_LIMIT            /* 没定义 */
    	if (bootlimit && (bootcount > bootlimit)) {
    		printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
    		       (unsigned)bootlimit);
    		s = getenv("altbootcmd");
    	} else
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    		s = getenv("bootcmd");            /* 得到环境变量里的启动参数 */
    
    	process_fdt_options(gd->fdt_blob);
    	stored_bootdelay = bootdelay;        /* 把bootdelay时间放到全局变量里面 */
    
    	return s;
    }

    1.1.3、

    
    void autoboot_command(const char *s)
    {
    	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
    
        /* 判断倒数计时 */
    	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
    #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    		int prev = disable_ctrlc(1);	/* disable Control C checking bootdelay期间ctrl + C无效*/
    #endif
            /* 运行bootcmd */
    		run_command_list(s, -1, 0);
    
    #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    		disable_ctrlc(prev);	/* restore Control C checking */
    #endif
    	}
    
    #ifdef CONFIG_MENUKEY            /* 没定义 */
    	if (menukey == CONFIG_MENUKEY) {
    		s = getenv("menucmd");
    		if (s)
    			run_command_list(s, -1, 0);
    	}
    #endif /* CONFIG_MENUKEY */
    }
    

     

    static int abortboot(int bootdelay)
    {
    	int abort = 0;
    
    	if (bootdelay >= 0)            /* bootdelay大于0,就要实现倒数计时 */
    		abort = __abortboot(bootdelay);
    
    #ifdef CONFIG_SILENT_CONSOLE        /* 没定义 */
    	if (abort)
    		gd->flags &= ~GD_FLG_SILENT;
    #endif
    
    	return abort;
    }

     

    
    static int __abortboot(int bootdelay)
    {
    	int abort = 0;
    	unsigned long ts;
    
    #ifdef CONFIG_MENUPROMPT        /* 没定义 */
    	printf(CONFIG_MENUPROMPT);
    #else
    	printf("Hit any key to stop autoboot: %2d ", bootdelay);   /* 时间打印提示信息,2d是以两位数打印 */
    #endif
    
    	/*
    	 * Check if key already pressed
    	 */
    	if (tstc()) {	/* we got a key press,判断串口是否有按键,有按键则终止 auto boot	*/
    		(void) getc();  /* consume input	*/
    		puts("\b\b\b 0");
    		abort = 1;	/* don't auto boot	*/
    	}
    
    	while ((bootdelay > 0) && (!abort)) {
    		--bootdelay;
    		/* delay 1000 ms */
    		ts = get_timer(0);
    		do {
    			if (tstc()) {	/* we got a key press	*/
    				abort  = 1;	/* don't auto boot	*/
    				bootdelay = 0;	/* no more delay	*/
    # ifdef CONFIG_MENUKEY
    				menukey = getc();
    # else
    				(void) getc();  /* consume input	*/
    # endif
    				break;
    			}
    			udelay(10000);    //10000us = 10 ms延时
    		} while (!abort && get_timer(ts) < 1000);    /* 有按键或超过1s则退出 */
    
    		printf("\b\b\b%2d ", bootdelay);        /* 覆盖掉之前的显示,并显示新的时间 */
    	}
    
    	putc('\n');
    
    	return abort;        /* 0表示没按键要执行bootcmd,1表示有按键 */
    }

     

     

    /* 执行命令行参数,和传进来的cmd有关 */
    int run_command_list(const char *cmd, int len, int flag)
    {
    	int need_buff = 1;
    	char *buff = (char *)cmd;	/* cast away const */
    	int rcode = 0;
    
    	if (len == -1) {
    		len = strlen(cmd);
    #ifdef CONFIG_HUSH_PARSER        /* 我们使用哈希表  */
    		/* hush will never change our string */
    		need_buff = 0;
    #else
    		/* the built-in parser will change our string if it sees \n */
    		need_buff = strchr(cmd, '\n') != NULL;
    #endif
    	}
    	if (need_buff) {
    		buff = malloc(len + 1);
    		if (!buff)
    			return 1;
    		memcpy(buff, cmd, len);
    		buff[len] = '\0';
    	}
    #ifdef CONFIG_HUSH_PARSER        /* 解析字符串,在哈希表中找到对应的函数 */
    	rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
    #else
    	/*
    	 * This function will overwrite any \n it sees with a \0, which
    	 * is why it can't work with a const char *. Here we are making
    	 * using of internal knowledge of this function, to avoid always
    	 * doing a malloc() which is actually required only in a case that
    	 * is pretty rare.
    	 */
    #ifdef CONFIG_CMDLINE
    	rcode = cli_simple_run_command_list(buff, flag);        /* 这个函数我放在后面分析 */
    #else
    	rcode = board_run_command(buff);
    #endif
    #endif
    	if (need_buff)
    		free(buff);
    
    	return rcode;
    }

     

    最终的函数

    void cli_loop(void)
    {
    #ifdef CONFIG_HUSH_PARSER            /* 一种是哈希表的文件结构分析命令行 */
    	parse_file_outer();
    	/* This point is never reached */
    	for (;;);    
    #elif defined(CONFIG_CMDLINE)        /* 一种是循环扫描方式 */
    	cli_simple_loop();
    #else
    	printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
    #endif /*CONFIG_HUSH_PARSER*/
    }

     

    哈希表的主要是一些算法上的,因为篇幅所限,这里就先跳过

    static int parse_file_outer(FILE *f)
    #else
    int parse_file_outer(void)
    #endif
    {
    	int rcode;
    	struct in_str input;
    #ifndef __U_BOOT__
    	setup_file_in_str(&input, f);
    #else
    	setup_file_in_str(&input);
    #endif
    	rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
    	return rcode;
    }

     

    我们简单分析一下循环命令行方式的

     

    
    #define CONFIG_SYS_CBSIZE		256    //smdkv210.h文件定义,命令行的最大长度
    
    void cli_simple_loop(void)
    {
    	static char lastcommand[CONFIG_SYS_CBSIZE + 1] = { 0, };
    
    	int len;
    	int flag;
    	int rc = 1;
    
    	for (;;) {
    		if (rc >= 0) {
    			/* Saw enough of a valid command to
    			 * restart the timeout. 命令行的rimeout主要是比如输入了一半命令好久没继续输入,则会把timeout超时提示重新输入,对主流程无关
    			 */
    			bootretry_reset_cmd_timeout();
    		}
            /* 读取命令,比较重要,这个下面分析,传入参数CONFIG_SYS_PROMPT是我们在smdkv210_defconfig里面定义的"SMDKV210 #",也就是命令行提示符 */
    		len = cli_readline(CONFIG_SYS_PROMPT);    
    
    		flag = 0;	/* assume no special flags for now */
    		if (len > 0)
    			strlcpy(lastcommand, console_buffer,    //把读到的命令拷贝到局部变量数组
    				CONFIG_SYS_CBSIZE + 1);
    		else if (len == 0)
    			flag |= CMD_FLAG_REPEAT;
    #ifdef CONFIG_BOOT_RETRY_TIME            //没定义
    		else if (len == -2) {
    			/* -2 means timed out, retry autoboot
    			 */
    			puts("\nTimed out waiting for command\n");
    # ifdef CONFIG_RESET_TO_RETRY
    			/* Reinit board to run initialization code again */
    			do_reset(NULL, 0, 0, NULL);
    # else
    			return;		/* retry autoboot */
    # endif
    		}
    #endif
    
    		if (len == -1)        /* -1是一些特殊的ctrl + c之类的特殊按键 */
    			puts("<INTERRUPT>\n");
    		else
    			rc = run_command_repeatable(lastcommand, flag);    /* 运行命令 */
    
    		if (rc <= 0) {
    			/* invalid command or not repeatable, forget it */
    			lastcommand[0] = 0;
    		}
    	}
    }

    读字符串

    //全局变量
    char console_buffer[CONFIG_SYS_CBSIZE + 1];	/* console I/O buffer	*/
    
    
    int cli_readline(const char *const prompt)
    {
    	/*
    	 * If console_buffer isn't 0-length the user will be prompted to modify
    	 * it instead of entering it from scratch as desired.
    	 */
    	console_buffer[0] = '\0';   /* 先初始化为0 */
        
        /* 三个参数分别是  "SMDKV210 #", 空字符串, 0 */
    	return cli_readline_into_buffer(prompt, console_buffer, 0);
    }

     

    
    int cli_readline_into_buffer(const char *const prompt, char *buffer,
    			     int timeout)
    {
    	char *p = buffer;
    
    /* 这个宏的作用是在使用终端时可以使用上下键,读取历史命令 */
    #ifdef CONFIG_CMDLINE_EDITING    
    	unsigned int len = CONFIG_SYS_CBSIZE;
    	int rc;
    	static int initted;
    
    	/*
    	 * History uses a global array which is not
    	 * writable until after relocation to RAM.
    	 * Revert to non-history version if still
    	 * running from flash.
    	 */
    	if (gd->flags & GD_FLG_RELOC) {
    		if (!initted) {
    			hist_init();        /* 初始化一些全局变量 */
    			initted = 1;
    		}
    
    		if (prompt)
    			puts(prompt);        /* 打印命令行提示符 "SMDKV210 # */
    
    		rc = cread_line(prompt, p, &len, timeout);        /* 读取命令行到上面传来的全局变量数组里(历史命令) */
    		return rc < 0 ? rc : len;
    
    	} else {
    #endif	/* CONFIG_CMDLINE_EDITING */
    	char *p_buf = p;
    	int	n = 0;				/* buffer index		*/
    	int	plen = 0;			/* prompt length	*/
    	int	col;				/* output column cnt	*/
    	char	c;
    
    	/* print prompt */
    	if (prompt) {
    		plen = strlen(prompt);         /* 打印命令行提示符 "SMDKV210 # */
    		puts(prompt);
    	}
    	col = plen;
    
    	for (;;) {
    		if (bootretry_tstc_timeout())
    			return -2;	/* timed out */
    		WATCHDOG_RESET();	/* Trigger watchdog, if needed */
    
    #ifdef CONFIG_SHOW_ACTIVITY
    		while (!tstc()) {
    			show_activity(0);
    			WATCHDOG_RESET();
    		}
    #endif
    		c = getc();
    
    		/*
    		 * Special character handling
    		 */
    		switch (c) {
    		case '\r':			/* Enter		*/
    		case '\n':
    			*p = '\0';
    			puts("\r\n");
    			return p - p_buf;
    
    		case '\0':			/* nul			*/
    			continue;
    
    		case 0x03:			/* ^C - break		*/
    			p_buf[0] = '\0';	/* discard input */
    			return -1;
    
    		case 0x15:			/* ^U - erase line	*/
    			while (col > plen) {
    				puts(erase_seq);
    				--col;
    			}
    			p = p_buf;
    			n = 0;
    			continue;
    
    		case 0x17:			/* ^W - erase word	*/
    			p = delete_char(p_buf, p, &col, &n, plen);
    			while ((n > 0) && (*p != ' '))
    				p = delete_char(p_buf, p, &col, &n, plen);
    			continue;
    
    		case 0x08:			/* ^H  - backspace	*/
    		case 0x7F:			/* DEL - backspace	*/
    			p = delete_char(p_buf, p, &col, &n, plen);
    			continue;
    
    		default:
    			/*
    			 * Must be a normal character then
    			 */
    			if (n < CONFIG_SYS_CBSIZE-2) {
    				if (c == '\t') {	/* expand TABs */
    #ifdef CONFIG_AUTO_COMPLETE
    					/*
    					 * if auto completion triggered just
    					 * continue
    					 */
    					*p = '\0';
    					if (cmd_auto_complete(prompt,
    							      console_buffer,
    							      &n, &col)) {
    						p = p_buf + n;	/* reset */
    						continue;
    					}
    #endif
    					puts(tab_seq + (col & 07));
    					col += 8 - (col & 07);
    				} else {
    					char __maybe_unused buf[2];
    
    					/*
    					 * Echo input using puts() to force an
    					 * LCD flush if we are using an LCD
    					 */
    					++col;
    					buf[0] = c;
    					buf[1] = '\0';
    					puts(buf);
    				}
    				*p++ = c;
    				++n;
    			} else {			/* Buffer full */
    				putc('\a');
    			}
    		}
    	}
    #ifdef CONFIG_CMDLINE_EDITING
    	}
    #endif
    }
    

     

    
    static int cread_line(const char *const prompt, char *buf, unsigned int *len,
    		int timeout)
    {
    	unsigned long num = 0;
    	unsigned long eol_num = 0;
    	unsigned long wlen;
    	char ichar;
    	int insert = 1;
    	int esc_len = 0;
    	char esc_save[8];
    	int init_len = strlen(buf);
    	int first = 1;
    
    	if (init_len)        /* 历史命令相关的 */
    		cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
    
    	while (1) {
    		if (bootretry_tstc_timeout())
    			return -2;	/* timed out */
    		if (first && timeout) {
    			uint64_t etime = endtick(timeout);
    
    			while (!tstc()) {	/* while no incoming data,反复的读命令,退出表明读到了字符输入 */
    				if (get_ticks() >= etime)
    					return -2;	/* timed out */
    				WATCHDOG_RESET();
    			}
    			first = 0;
    		}
            
    		ichar = getcmd_getch();        // getcmd_getch == getc
    
            /* 如果输入的是回车换行则退出break */
    		if ((ichar == '\n') || (ichar == '\r')) {
    			putc('\n');
    			break;
    		}
    
    		/*
    		 * handle standard linux xterm esc sequences for arrow key, etc.
             * 兼容linux的  xterm的关系不大
    		 */
    		if (esc_len != 0) {
    			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
    
    			if (esc_len == 1) {
    				if (ichar == '[' || ichar == 'O')
    					act = ESC_SAVE;
    			} else if (esc_len == 2) {
    				switch (ichar) {
    				case 'D':	/* <- key */
    					ichar = CTL_CH('b');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^B handler */
    				case 'C':	/* -> key */
    					ichar = CTL_CH('f');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^F handler */
    				case 'H':	/* Home key */
    					ichar = CTL_CH('a');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^A handler */
    				case 'F':	/* End key */
    					ichar = CTL_CH('e');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^E handler */
    				case 'A':	/* up arrow */
    					ichar = CTL_CH('p');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^P handler */
    				case 'B':	/* down arrow */
    					ichar = CTL_CH('n');
    					act = ESC_CONVERTED;
    					break;	/* pass off to ^N handler */
    				case '1':
    				case '3':
    				case '4':
    				case '7':
    				case '8':
    					if (esc_save[1] == '[') {
    						/* see if next character is ~ */
    						act = ESC_SAVE;
    					}
    					break;
    				}
    			} else if (esc_len == 3) {
    				if (ichar == '~') {
    					switch (esc_save[2]) {
    					case '3':	/* Delete key */
    						ichar = CTL_CH('d');
    						act = ESC_CONVERTED;
    						break;	/* pass to ^D handler */
    					case '1':	/* Home key */
    					case '7':
    						ichar = CTL_CH('a');
    						act = ESC_CONVERTED;
    						break;	/* pass to ^A handler */
    					case '4':	/* End key */
    					case '8':
    						ichar = CTL_CH('e');
    						act = ESC_CONVERTED;
    						break;	/* pass to ^E handler */
    					}
    				}
    			}
    
    			switch (act) {
    			case ESC_SAVE:
    				esc_save[esc_len++] = ichar;
    				continue;
    			case ESC_REJECT:
    				esc_save[esc_len++] = ichar;
    				cread_add_str(esc_save, esc_len, insert,
    					      &num, &eol_num, buf, *len);
    				esc_len = 0;
    				continue;
    			case ESC_CONVERTED:
    				esc_len = 0;
    				break;
    			}
    		}
    
            /* 输入的是特殊测什么ctrl +c,退格,ctrl+v之类,特殊处理*/
    		switch (ichar) {
    		case 0x1b:
    			if (esc_len == 0) {
    				esc_save[esc_len] = ichar;
    				esc_len = 1;
    			} else {
    				puts("impossible condition #876\n");
    				esc_len = 0;
    			}
    			break;
    
    		case CTL_CH('a'):
    			BEGINNING_OF_LINE();
    			break;
    		case CTL_CH('c'):	/* ^C - break */
    			*buf = '\0';	/* discard input */
    			return -1;
    		case CTL_CH('f'):
    			if (num < eol_num) {
    				getcmd_putch(buf[num]);
    				num++;
    			}
    			break;
    		case CTL_CH('b'):
    			if (num) {
    				getcmd_putch(CTL_BACKSPACE);
    				num--;
    			}
    			break;
    		case CTL_CH('d'):
    			if (num < eol_num) {
    				wlen = eol_num - num - 1;
    				if (wlen) {
    					memmove(&buf[num], &buf[num+1], wlen);
    					putnstr(buf + num, wlen);
    				}
    
    				getcmd_putch(' ');
    				do {
    					getcmd_putch(CTL_BACKSPACE);
    				} while (wlen--);
    				eol_num--;
    			}
    			break;
    		case CTL_CH('k'):
    			ERASE_TO_EOL();
    			break;
    		case CTL_CH('e'):
    			REFRESH_TO_EOL();
    			break;
    		case CTL_CH('o'):
    			insert = !insert;
    			break;
    		case CTL_CH('x'):
    		case CTL_CH('u'):
    			BEGINNING_OF_LINE();
    			ERASE_TO_EOL();
    			break;
    		case DEL:
    		case DEL7:
    		case 8:
    			if (num) {
    				wlen = eol_num - num;
    				num--;
    				memmove(&buf[num], &buf[num+1], wlen);
    				getcmd_putch(CTL_BACKSPACE);
    				putnstr(buf + num, wlen);
    				getcmd_putch(' ');
    				do {
    					getcmd_putch(CTL_BACKSPACE);
    				} while (wlen--);
    				eol_num--;
    			}
    			break;
    		case CTL_CH('p'):
    		case CTL_CH('n'):
    		{
    			char *hline;
    
    			esc_len = 0;
    
    			if (ichar == CTL_CH('p'))
    				hline = hist_prev();
    			else
    				hline = hist_next();
    
    			if (!hline) {
    				getcmd_cbeep();
    				continue;
    			}
    
    			/* nuke the current line */
    			/* first, go home */
    			BEGINNING_OF_LINE();
    
    			/* erase to end of line */
    			ERASE_TO_EOL();
    
    			/* copy new line into place and display */
    			strcpy(buf, hline);
    			eol_num = strlen(buf);
    			REFRESH_TO_EOL();
    			continue;
    		}
    #ifdef CONFIG_AUTO_COMPLETE
    		case '\t': {
    			int num2, col;
    
    			/* do not autocomplete when in the middle */
    			if (num < eol_num) {
    				getcmd_cbeep();
    				break;
    			}
    
    			buf[num] = '\0';
    			col = strlen(prompt) + eol_num;
    			num2 = num;
    			if (cmd_auto_complete(prompt, buf, &num2, &col)) {
    				col = num2 - num;
    				num += col;
    				eol_num += col;
    			}
    			break;
    		}
    #endif
    		default:
                /* 把字符放到buf数组里面去 */
    			cread_add_char(ichar, insert, &num, &eol_num, buf,
    				       *len);
    			break;        //
    		}
    	}
    	*len = eol_num;
    	buf[eol_num] = '\0';	/* lose the newline */
    
        /* 保存参数 */
    	if (buf[0] && buf[0] != CREAD_HIST_CHAR)
    		cread_add_to_hist(buf);
    	hist_cur = hist_add_idx;
    
    	return 0;
    }
    

     

     

    运行命令

     

    int run_command_repeatable(const char *cmd, int flag)   //flag = 0
    {
    #ifndef CONFIG_HUSH_PARSER
    	return cli_simple_run_command(cmd, flag);        /* 分析写个,不分析哈希的 */
    #else
    	/*
    	 * parse_string_outer() returns 1 for failure, so clean up
    	 * its result.
    	 */
    	if (parse_string_outer(cmd,
    			       FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP))
    		return -1;
    
    	return 0;
    #endif
    }

     

    int cli_simple_run_command(const char *cmd, int flag)
    {
    	char cmdbuf[CONFIG_SYS_CBSIZE];	/* working copy of cmd		*/
    	char *token;			/* start of token in cmdbuf	*/
    	char *sep;			/* end of token (separator) in cmdbuf */
    	char finaltoken[CONFIG_SYS_CBSIZE];
    	char *str = cmdbuf;
    	char *argv[CONFIG_SYS_MAXARGS + 1];	/* NULL terminated	*/
    	int argc, inquotes;
    	int repeatable = 1;
    	int rc = 0;
    
    	debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd);
    	if (DEBUG_PARSER) {
    		/* use puts - string may be loooong */
    		puts(cmd ? cmd : "NULL");
    		puts("\"\n");
    	}
    	clear_ctrlc();		/* forget any previous Control C, */
    
    	if (!cmd || !*cmd)        //错误判断
    		return -1;	/* empty command */
    
    	if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {        //错误判断
    		puts("## Command too long!\n");
    		return -1;
    	}
    
    	strcpy(cmdbuf, cmd);        /* 拷贝一份局部的cmd,这样可以随意修改 */
    
    	/* Process separators and check for invalid
    	 * repeatable commands
    	 */
    
    	debug_parser("[PROCESS_SEPARATORS] %s\n", cmd);
    	while (*str) {
    		/*
    		 * Find separator, or string end
    		 * Allow simple escape of ';' by writing "\;
                     * 可以通过';'间隔,一次执行多条命令,这里是解析开如果有';'就解析成多条命令
    		 */
    		for (inquotes = 0, sep = str; *sep; sep++) {
    			if ((*sep == '\'') &&
    			    (*(sep - 1) != '\\'))
    				inquotes = !inquotes;
    
    			if (!inquotes &&
    			    (*sep == ';') &&	/* separator		*/
    			    (sep != str) &&	/* past string start	*/
    			    (*(sep - 1) != '\\'))	/* and NOT escaped */
    				break;
    		}
    
    		/*
    		 * Limit the token to data between separators
             * 
    		 */
    		token = str;
    		if (*sep) {
    			str = sep + 1;	/* start of command for next pass */
    			*sep = '\0';
    		} else {
    			str = sep;	/* no more commands for next pass */
    		}
    		debug_parser("token: \"%s\"\n", token);
    
    		/* find macros in this token and replace them,里面各种解析,主要是解析一些宏,比如uboot,kernel之类要解析成对应的地址等,通过finaltoken返回 */
    		cli_simple_process_macros(token, finaltoken);
    
    		/* Extract arguments,分解finaltoken,里面的命令行带的参数,分解成argc和argv类型 */
    		argc = cli_simple_parse_line(finaltoken, argv);
    		if (argc == 0) {
    			rc = -1;	/* no command at all */
    			continue;
    		}
            /* 真正的执行命令和前面解析好的参数 */
    		if (cmd_process(flag, argc, argv, &repeatable, NULL))
    			rc = -1;
    
    		/* Did the user stop this? */
    		if (had_ctrlc())
    			return -1;	/* if stopped then not repeatable */
    	}
    
    	return rc ? rc : repeatable;
    }

     

    下面要用到的一些数据结构

    enum command_ret_t {
    	CMD_RET_SUCCESS,	/* 0 = Success */
    	CMD_RET_FAILURE,	/* 1 = Failure */
    	CMD_RET_USAGE = -1,	/* Failure, please report 'usage' error */
    };
    
    /* uboot中的所有命令都是采用这种形式,所以这个结构体很重要 */
    struct cmd_tbl_s {
    	char		*name;		/* Command Name,比如bootcmd,help之类*/
    	int		maxargs;	/* maximum number of arguments,每个命令都有最大参数限制	*/
    	int		repeatable;	/* autorepeat allowed?	,是否支持可重复,即按回车键会再次执行	*/
    					/* Implementation function,正真的处理命令的函数	*/
    	int		(*cmd)(struct cmd_tbl_s *, int, int, char * const []);
    	char		*usage;		/* Usage message	(short)	,短帮助信息*/
    #ifdef	CONFIG_SYS_LONGHELP
    	char		*help;		/* Help  message	(long)	长帮助信息 */
    #endif
    #ifdef CONFIG_AUTO_COMPLETE
    	/* do auto completion on the arguments ,命令自动补全,目前我们是没定义的,可以在smdkv210.h中定义 */
    	int		(*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
    #endif
    };
    
    typedef struct cmd_tbl_s	cmd_tbl_t;
    

     

    
    enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
    			       int *repeatable, ulong *ticks)
    {
    	enum command_ret_t rc = CMD_RET_SUCCESS;
    	cmd_tbl_t *cmdtp;
    
    	/* Look up command in command table */
    	cmdtp = find_cmd(argv[0]);        /* argv[0],就是命令本身,这里通过这个字符串找到对应的cmd_tbl_t  */
    	if (cmdtp == NULL) {        /* NULL表示没找到 */
    		printf("Unknown command '%s' - try 'help'\n", argv[0]);
    		return 1;
    	}
    
    	/* found - check max args */
    	if (argc > cmdtp->maxargs)        /* 检查参数个数 */
    		rc = CMD_RET_USAGE;
    
    #if defined(CONFIG_CMD_BOOTD)
    	/* avoid "bootd" recursion */
    	else if (cmdtp->cmd == do_bootd) {    /* 如果定义这个命令的话,则执行bootm命令 */
    		if (flag & CMD_FLAG_BOOTD) {    
    			puts("'bootd' recursion detected\n");
    			rc = CMD_RET_FAILURE;        /* 执行bootm是不会返回的,返回就说明错了 */
    		} else {
    			flag |= CMD_FLAG_BOOTD;
    		}
    	}
    #endif
    
    	/* If OK so far, then do the command */
    	if (!rc) {
    		if (ticks)
    			*ticks = get_timer(0);
    		rc = cmd_call(cmdtp, flag, argc, argv);    /* 执行找到的命令,并把上面解析好的参数传给它 */
    		if (ticks)
    			*ticks = get_timer(*ticks);
    		*repeatable &= cmdtp->repeatable;    /* 执行完上一条命令,则把上一条命令的repet置位给大循环里面传过来的 */
    	}
    	if (rc == CMD_RET_USAGE)
    		rc = cmd_usage(cmdtp);
    	return rc;
    }
    

     

    查找到命令

    cmd_tbl_t *find_cmd(const char *cmd)
    {
    	cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
    	const int len = ll_entry_count(cmd_tbl_t, cmd);
    	return find_cmd_tbl(cmd, start, len);
    }
    

    在分析这命令之前,我们先看一下uboot的链接脚本布局

    这里有个u_boot_list*段

    我们分析一个简单的uboot的命令实现

    
    const char __weak version_string[] = U_BOOT_VERSION_STRING;
    
    /* 真正的函数,所有的函数格式都是cmd_tbl_t里面的函数指针一样 */
    static int do_version(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    	printf("\n%s\n", version_string);
    #ifdef CC_VERSION_STRING
    	puts(CC_VERSION_STRING "\n");
    #endif
    #ifdef LD_VERSION_STRING
    	puts(LD_VERSION_STRING "\n");
    #endif
    #ifdef CONFIG_SYS_COREBOOT
    	printf("coreboot-%s (%s)\n", lib_sysinfo.version, lib_sysinfo.build);
    #endif
    	return 0;
    }
    
    /* 这句是真正的定义uboot的一个命令,U_BOOT_CMD是一个宏下面我们一次把这个version命令展开 */
    U_BOOT_CMD(
    	version,	1,		1,	do_version,
    	"print monitor, compiler and linker version",
    	""
    );
    

    看起来很负责的样子,我们一步一步来展开

    
    #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
    				_usage, _help, _comp)			\
    		{ #_name, _maxargs, _rep, _cmd, _usage,			\
    			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
    
    #define ll_entry_declare(_type, _name, _list)				\
    	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
    			__attribute__((unused,				\
    			section(".u_boot_list_2_"#_list"_2_"#_name)))
    
    
    #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
    	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
    		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
    						_usage, _help, _comp);
    
    
    #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
    	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

    第一步

    U_BOOT_CMD(
    	version,	1,		1,	do_version,
    	"print monitor, compiler and linker version",
    	""
    );
    
    #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
    	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
    
    展开后
    #define U_BOOT_CMD(version, 1, 1, do_version,"print monitor, compiler and linker version", "")		\
    	U_BOOT_CMD_COMPLETE(version, 1, 1, do_version, "print monitor, compiler and linker version", "", NULL)
    

    第二步

    U_BOOT_CMD_COMPLETE(version, 1, 1, do_version, "print monitor, compiler and linker version", "", NULL)
    
    
    #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
    	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
    		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
    						_usage, _help, _comp);
    
    展开后
    #define U_BOOT_CMD_COMPLETE(version, 1, 1, do_version, "print monitor, compiler and linker version", "", NULL) \
    	ll_entry_declare(cmd_tbl_t, version, cmd) =			\
    		U_BOOT_CMD_MKENT_COMPLETE(version, 1, 1, do_version,	\
    						"print monitor, compiler and linker version", "", NULL);
    

    第三步

    	ll_entry_declare(cmd_tbl_t, version, cmd) =			\
    		U_BOOT_CMD_MKENT_COMPLETE(version, 1, 1, do_version,	\
    						"print monitor, compiler and linker version", "", NULL);
    
    #define ll_entry_declare(_type, _name, _list)				\
    	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
    			__attribute__((unused,				\
    			section(".u_boot_list_2_"#_list"_2_"#_name)))
    
    
    #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
    				_usage, _help, _comp)			\
    		{ #_name, _maxargs, _rep, _cmd, _usage,			\
    			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
    这里面有量个宏,我们分两次分别展开
    第一个
    ll_entry_declare(cmd_tbl_t, version, cmd)
    
    #define ll_entry_declare(_type, _name, _list)				\
    	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
    			__attribute__((unused,				\
    			section(".u_boot_list_2_"#_list"_2_"#_name)))
    
    
    #define ll_entry_declare(cmd_tbl_t, version, cmd)				\
    	cmd_tbl_t _u_boot_list_2_cmd_2_version __aligned(4)		\
    			__attribute__((unused,				\
    			section(".u_boot_list_2_cmd_2_version")))
    第二个
    U_BOOT_CMD_MKENT_COMPLETE(version, 1, 1, do_version,	\
    						"print monitor, compiler and linker version", "", NULL);
    
    #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
    				_usage, _help, _comp)			\
    		{ #_name, _maxargs, _rep, _cmd, _usage,			\
    			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
    
    #define U_BOOT_CMD_MKENT_COMPLETE(version, 1, 1, do_version,		\
    				"print monitor, compiler and linker version", "", NULL)			\
    		{ "version", 1, 1, do_version, "print monitor, compiler and linker version",			\
    			_CMD_HELP("") _CMD_COMPLETE(NULL) }
    
    
    
    

    第四步、合并上一步的两个

    #define ll_entry_declare(cmd_tbl_t, version, cmd)				\
    	cmd_tbl_t _u_boot_list_2_cmd_2_version __aligned(4)		\
    			__attribute__((unused,				\
    			section(".u_boot_list_2_cmd_2_version")))
    
    #define U_BOOT_CMD_MKENT_COMPLETE(version, 1, 1, do_version,		\
    				"print monitor, compiler and linker version", "", NULL)			\
    		{ "version", 1, 1, do_version, "print monitor, compiler and linker version",			\
    			_CMD_HELP("") _CMD_COMPLETE(NULL) }
    
    
    合并后
    cmd_tbl_t _u_boot_list_2_cmd_2_version __aligned(4) __attribute__ ((unused,section(".u_boot_list_2_cmd_2_version")))
    {
          "version", 
          1, 
          1, 
          do_version, 
          "print monitor, compiler and linker version",			
          _CMD_HELP("") _CMD_COMPLETE(NULL) 
    }

    第五步

    #ifdef CONFIG_AUTO_COMPLETE    /* 假设都定义了 */
    # define _CMD_COMPLETE(x) x,
    #else
    # define _CMD_COMPLETE(x)
    #endif
    #ifdef CONFIG_SYS_LONGHELP
    # define _CMD_HELP(x) x,
    #else
    # define _CMD_HELP(x)
    #endif
    
    
    cmd_tbl_t _u_boot_list_2_cmd_2_version __aligned(4) __attribute__ ((unused,section(".u_boot_list_2_cmd_2_version")))
    {
          "version", 
          1, 
          1, 
          do_version, 
          "print monitor, compiler and linker version",			
          _CMD_HELP("") _CMD_COMPLETE(NULL) 
    }
    展开后
    
    
    cmd_tbl_t _u_boot_list_2_cmd_2_version __aligned(4) __attribute__ ((unused,section(".u_boot_list_2_cmd_2_version")))
    {
          "version", 
          1, 
          1, 
          do_version, 
          "print monitor, compiler and linker version",			
          "",
          NULL,        /* 自动命令补全 */
    }
    

    通过上面的五步分析可以看到,每个命令结构体都会被__sttribute__定义在一个新的段里面,这里可以看到每个命令的段都是不同的,那怎么统一管理呢?

    看一下连接脚本就懂了

    用*,只要前面的u_boot_list段相同,后面的不同用*则忽略,这样吧所有的命令都定义在了这个段里面了。

     

    下面要用到一个宏在这里就一块分析了

    #define ll_entry_count(_type, _list)					\
    	({								\
    		_type *start = ll_entry_start(_type, _list);		\
    		_type *end = ll_entry_end(_type, _list);		\
    		unsigned int _ll_result = end - start;			\
    		_ll_result;						\
    	})
    
    假设传给它的还是cmd_tbl_t  和version
    
    #define ll_entry_count(cmd_tbl_t  , version)					\
    	({								\
    		cmd_tbl_t   *start = ll_entry_start(cmd_tbl_t  , version);		\
    		cmd_tbl_t   *end = ll_entry_end(cmd_tbl_t  , version);		\
    		unsigned int _ll_result = end - version;			\
    		_ll_result;						\
    	})
    
    
    
    #define ll_entry_start(_type, _list)					\
    ({									\
    	static char start[0] __aligned(4) __attribute__((unused,	\
    		section(".u_boot_list_2_"#_list"_1")));			\
    	(_type *)&start;						\
    })
    替换后
    #define ll_entry_start(cmd_tbl_t  , version)					\
    ({									\
    	static char start[0] __aligned(4) __attribute__((unused,	\
    		section(".u_boot_list_2_version_1")));			\
    	(cmd_tbl_t  *)&start;						\
    })
    
    继续
    static char start[0] __aligned(4) __attribute__ ((unused,section(".u_boot_list_2_version_1")));			
    (cmd_tbl_t  *)&start;	
    
    
    
    
    #define ll_entry_end(_type, _list)					\
    ({									\
    	static char end[0] __aligned(4) __attribute__((unused,		\
    		section(".u_boot_list_2_"#_list"_3")));			\
    	(_type *)&end;							\
    })
    替换
    #define ll_entry_end(cmd_tbl_t , version)					\
    ({									\
    	static char end[0] __aligned(4) __attribute__((unused,		\
    		section(".u_boot_list_2_version_3")));			\
    	(cmd_tbl_t *)&end;							\
    })
    继续  定义一个指针,用来存放 该段的地址
    static char end[0] __aligned(4) __attribute__ ((unused, section(".u_boot_list_2_version_3")));		
    	(cmd_tbl_t *)&end;							
    
    
    
    #define ll_entry_count(cmd_tbl_t  , version)					\
    	({								\
    		cmd_tbl_t   *start = ll_entry_start(cmd_tbl_t  , version);		\
    		cmd_tbl_t   *end = ll_entry_end(cmd_tbl_t  , version);		\
    		unsigned int _ll_result = end - version;			\
    		_ll_result;						\
    	})
    整体替换
    #define ll_entry_count(cmd_tbl_t  , version)					\
    	({								\
    		cmd_tbl_t   *start = &u_boot_list_2_version_1;		\
    		cmd_tbl_t   *end = &u_boot_list_2_version_3;		\
    		unsigned int _ll_result = end - version;			\
    		_ll_result;						\
    	})
    

    为什么一个boot_list要定义这么多的1、2、3这么多的段,看一下官方的举例解释

    /**
     * A linker list is constructed by grouping together linker input
     * sections, each containing one entry of the list. Each input section
     * contains a constant initialized variable which holds the entry's
     * content. Linker list input sections are constructed from the list
     * and entry names, plus a prefix which allows grouping all lists
     * together. Assuming _list and _entry are the list and entry names,
     * then the corresponding input section name is
     *
     *   .u_boot_list_ + 2_ + @_list + _2_ + @_entry
     *
     * and the C variable name is
     *
     *   _u_boot_list + _2_ + @_list + _2_ + @_entry
     *
     * This ensures uniqueness for both input section and C variable name.
     *
     * Note that the names differ only in the first character, "." for the
     * section and "_" for the variable, so that the linker cannot confuse
     * section and symbol names. From now on, both names will be referred
     * to as
     *
     *   %u_boot_list_ + 2_ + @_list + _2_ + @_entry
     *
     * Entry variables need never be referred to directly.
     *
     * The naming scheme for input sections allows grouping all linker lists
     * into a single linker output section and grouping all entries for a
     * single list.
     *
     * Note the two '_2_' constant components in the names: their presence
     * allows putting a start and end symbols around a list, by mapping
     * these symbols to sections names with components "1" (before) and
     * "3" (after) instead of "2" (within).
     * Start and end symbols for a list can generally be defined as
     *
     *   %u_boot_list_2_ + @_list + _1_...
     *   %u_boot_list_2_ + @_list + _3_...
     *
     * Start and end symbols for the whole of the linker lists area can be
     * defined as
     *
     *   %u_boot_list_1_...
     *   %u_boot_list_3_...
     *
     * Here is an example of the sorted sections which result from a list
     * "array" made up of three entries : "first", "second" and "third",
     * iterated at least once.
     *
     *   .u_boot_list_2_array_1
     *   .u_boot_list_2_array_2_first
     *   .u_boot_list_2_array_2_second
     *   .u_boot_list_2_array_2_third
     *   .u_boot_list_2_array_3
     *
     * If lists must be divided into sublists (e.g. for iterating only on
     * part of a list), one can simply give the list a name of the form
     * 'outer_2_inner', where 'outer' is the global list name and 'inner'
     * is the sub-list name. Iterators for the whole list should use the
     * global list name ("outer"); iterators for only a sub-list should use
     * the full sub-list name ("outer_2_inner").
     *
     * Here is an example of the sections generated from a global list
     * named "drivers", two sub-lists named "i2c" and "pci", and iterators
     * defined for the whole list and each sub-list:
     *
     *   %u_boot_list_2_drivers_1
     *   %u_boot_list_2_drivers_2_i2c_1
     *   %u_boot_list_2_drivers_2_i2c_2_first
     *   %u_boot_list_2_drivers_2_i2c_2_first
     *   %u_boot_list_2_drivers_2_i2c_2_second
     *   %u_boot_list_2_drivers_2_i2c_2_third
     *   %u_boot_list_2_drivers_2_i2c_3
     *   %u_boot_list_2_drivers_2_pci_1
     *   %u_boot_list_2_drivers_2_pci_2_first
     *   %u_boot_list_2_drivers_2_pci_2_second
     *   %u_boot_list_2_drivers_2_pci_2_third
     *   %u_boot_list_2_drivers_2_pci_3
     *   %u_boot_list_2_drivers_3
     */

    哇,还来2可以实现很多个。好吧,我们还是分析点简单的。

     

    下面再分析find_cmd就会简单有一些

    cmd_tbl_t *find_cmd(const char *cmd)
    {
         /* 可以看到上面ll_entry_start/ll_entry_start这个宏解析后就是一个和命令名称相关的唯一的段 */
    	cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd); 
    	const int len = ll_entry_count(cmd_tbl_t, cmd);    /* 假设我们的长度就1个命令 */
    	return find_cmd_tbl(cmd, start, len);        /* 命令的地址和长度都有了,接下来就直接搜索就可以 */
    }
    

     

    find_cmd_tbl

    /* find command table entry for a command */
    cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
    {
    #ifdef CONFIG_CMDLINE
    	cmd_tbl_t *cmdtp;
    	cmd_tbl_t *cmdtp_temp = table;	/* Init value */
    	const char *p;
    	int len;
    	int n_found = 0;
    
    	if (!cmd)        /* 检查参数有效 */
    		return NULL;
    	/*
    	 * Some commands allow length modifiers (like "cp.b");
    	 * compare command name only until first dot.
         * 上面注释说的比较明白了,一些命令会有多个命令既,比如cp.b cp.w  cp.l,命令集每个的后缀和命令名用.间隔
    	 */
    
        /* 如果是命令集,只要.前面命令的长度 */
    	len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
        
        /* 循环搜索,看命令集(1个命令也可以这样称呼)中的名字能否匹配上 */
    	for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {
    		if (strncmp(cmd, cmdtp->name, len) == 0) {
    			if (len == strlen(cmdtp->name))        /* 命令集中的名字长度和这个一致 */
    				return cmdtp;	/* full match,正确返回这个命令结构体的地址 */
    
    			cmdtp_temp = cmdtp;	/* abbreviated command ? */
    			n_found++;
    		}
    	}
    	if (n_found == 1) {			/* exactly one match */
    		return cmdtp_temp;
    	}
    #endif /* CONFIG_CMDLINE */
    
    	return NULL;	/* not found or ambiguous command,没找到则返回NULL */
    }

    通过查找,如果找到命令了,则就要执行命令了

    static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
    {
    	int result;
        /* 执行函数命令很简单,直接一个指针函数调用,具体函数在命令实现中 */
    	result = (cmdtp->cmd)(cmdtp, flag, argc, argv);        /* 正常命令执行返回0 */    
    	if (result)
    		debug("Command failed, result=%d\n", result);
    	return result;
    

     

     

    展开全文
  • 本课程为UBOOT移植部分,为后续课程打基础
  • uboot详解

    千次阅读 2013-10-24 08:50:50
    一个嵌入式的存储设备通过通常包括四个分区: 第一分区:存放的当然是u-boot 第二个分区:存放着u-boot要传给系统内核的参数 第三个分区:是系统内核(kernel) 第四个分区:则是根文件系统 ...

    参考: http://blog.chinaunix.net/uid-26935820-id-3280803.html

    参考: http://www.linuxidc.com/Linux/2012-08/68707.htm

    参考: http://blog.chinaunix.net/uid-26833883-id-3160714.html

    一个嵌入式的存储设备通过通常包括四个分区:

    一:存放的是u-boot  二:存放u-boot要传给系统内核的参数  三:系统内核(kernel)   四:根文件系统


    Bootloader的启动方式

    Bootloader的启动方式主要有:1. 网络启动方式  2. 磁盘启动方式  3.Flash启动方式

    1、网络启动方式


    主机和目标板,他们中间通过网络来连接,首先目标板的DHCP/BIOS通过BOOTP服来为Bootloader分配IP地址,配置网络参数,这样才能支持网络传输功能。我们使用的u-boot可以直接设置网络参数,因此这里就不用使用DHCP的方式动态分配IP了。接下来目标板的Bootloader通过TFTP服务将内核映像下载到目标板上,然后通过网络文件系统来建立主机与目标板之间的文件通信过程,之后的系统更新通常也是使用Boot Loader的这种工作模式。工作于这种模式下的Boot Loader通常都会向它的终端用户提供一个简单的命令行接口。

    2、磁盘启动方式

    这种方式主要是用在台式机和服务器上的,这些计算机都使用BIOS引导,并且使用磁盘作为存储介质,这里面两个重要的用来启动linux的有LILO和GRUB,这里就不再具体说明了。

    3、Flash启动方式

    这是我们最常用的方式。Flash有NOR Flash和NAND Flash两种。NOR Flash可以支持随机访问,所以代码可以直接在Flash上执行,Bootloader一般是存储在Flash芯片上的。另外Flash上还存储着参数、内核映像和文件系统。这种启动方式与网络启动方式之间的不同之处就在于,在网络启动方式中,内核映像和文件系统首先是放在主机上的,然后经过网络传输下载进目标板的,而这种启动方式中内核映像和文件系统则直接是放在Flash中的,这两点在我们u-boot的使用过程中都用到了。

    u-boot源代码的目录结构




    参考 LMA VMA                                                    


    uboot工作过程 

    大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。 但从最终用户的角度看,Boot Loader 的作用就是:用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

    (一)启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

    (二)下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader保存到目标机的RAM 中,然后再被 BootLoader写到目标机上的FLASH类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。这种工作模式通常在第一次安装内核与跟文件系统时使用。或者在系统更新时使用。进行嵌入式系统调试时一般也让bootloader工作在这一模式下。U­Boot 这样功能强大的 Boot Loader 同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。

     

    本文以uboot-1.1.6分析:

    uboot编译流程:

    (1) 首先编译 cpu/$(CPU)/start.S, 对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件

    (2) 对于 平台/开发板 相关的每个目录\每个通用目录都使用它们各自的Makefile生成相应的库

    (3) 将1.2步骤生成的 xxx.o  xxx.a  文件按照 board/$(BOARDDIR)/config.mk文件指定的代码段起始地址, board/$(BOARDDIR)/U-Boot.lds链接脚本来进行链接

    (4) 第3步得到的是ELF格式的U-Boot, 后面Makefile还会将它转换为二进制格式,S-Record格式


    uboot工作流程:

    (1)第一阶段的功能

    Ø 硬件设备初始化

    Ø 加载U-Boot第二阶段代码到RAM空间

    Ø 设置好栈

    Ø 跳转到第二阶段代码入口

    (2)第二阶段的功能

    Ø 初始化本阶段使用的硬件设备

    Ø 检测系统内存映射

    Ø 将内核从Flash读取到RAM中

    Ø 为内核设置启动参数

    Ø 调用内核

    图 2.1 U-Boot启动第一阶段流程


    根据 board/smdk2410/u-boot.lds中指定的链接方式:

    OUTPUT_ARCH(arm)
    ENTRY(_start)
    SECTIONS
    {
    	. = 0x00000000;
    
    	. = ALIGN(4);
    	.text      :
    	{
    	  cpu/arm920t/start.o	(.text)
    	  *(.text)
    	}
    
    	. = ALIGN(4);
    	.rodata : { *(.rodata) }
    
    	. = ALIGN(4);
    	.data : { *(.data) }
    
    	. = ALIGN(4);
    	.got : { *(.got) }
    
    	. = .;
    	__u_boot_cmd_start = .;
    	.u_boot_cmd : { *(.u_boot_cmd) }
    	__u_boot_cmd_end = .;
    
    	. = ALIGN(4);
    	__bss_start = .;
    	.bss : { *(.bss) }
    	_end = .;
    }

    第一个链接的是cpu/arm920t/start.o,代码段第一个存放的也是start.S, 因此u-boot.bin的入口代码在cpu/arm920t/start.S中。下面我们来分析cpu/arm920t/start.S的执行

    一、 uboot第一阶段代码分析

    1. 硬件设备初始化

    (1)设置异常向量

    cpu/arm920t/start.S开头有如下的代码:

    .globl _start
    _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/* irq中断向量 */
    	ldr	pc, _fiq/* fiq中断向量 */
    /* 中断向量表入口地址 */
    _undefined_instruction:	.word 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
    
    	.balignl 16,0xdeadbeef


    以上代码设置了ARM异常向量表,各个异常向量介绍如下:

    表 2.1 ARM异常向量表

    地址

    异常

    进入模式

    描述

    0x00000000

    复位

    管理模式

    复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行

    0x00000004

    未定义指令

    未定义模式

    遇到不能处理的指令时,产生未定义指令异常

    0x00000008

    软件中断

    管理模式

    执行SWI指令产生,用于用户模式下的程序调用特权操作指令

    0x0000000c

    预存指令

    中止模式

    处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常

    0x00000010

    数据操作

    中止模式

    处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常

    0x00000014

    未使用

    未使用

    未使用

    0x00000018

    IRQ

    IRQ

    外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常

    0x0000001c

    FIQ

    FIQ

    快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常

    在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。

    其中复位异常向量的指令“b reset”决定了U-Boot启动后将自动跳转到标号“reset”处执行。

    2CPU进入SVC模式

    reset:
    	/*
    	 * set the cpu to SVC32 mode
    	 */
    	mrs	r0,cpsr
    	bic	r0,r0,#0x1f	/*工作模式位清零 */
    	orr	r0,r0,#0xd3	/*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */
    	msr	cpsr,r0
    以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置1,从而屏蔽了IRQFIQ中断

    3)设置控制寄存器地址

    /* turn off the watchdog */
    #if defined(CONFIG_S3C2400)
    # define pWTCON		0x15300000
    # define INTMSK		0x14400008	/* Interupt-Controller base addresses */
    # define CLKDIVN	0x14800014	/* clock divisor register */
    #elif defined(CONFIG_S3C2410)
    # define pWTCON		0x53000000
    # define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
    # define INTSUBMSK	0x4A00001C
    # define CLKDIVN	0x4C000014	/* clock divisor register */
    #endif

    对于s3c2440,以上代码完成了WATCHDOGINTMSKINTSUBMSKCLKDIVN四个寄存器的地址的宏定义

    4)关闭看门狗

    #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    	ldr     r0, =pWTCON
    	mov     r1, #0x0
    	str     r1, [r0]	/*看门狗控制器的最低位为0时,看门狗不输出复位信号 */
    以上代码向看门狗控制寄存器写入0,关闭看门狗。否则在U-Boot启动过程中,CPU将不断重启。

    5)屏蔽中断

    /*
    * mask all IRQs by setting all bits in the INTMR - default
    */
    mov r1, #0xffffffff /* 某位被置1则对应的中断被屏蔽 */
    ldr r0, =INTMSK
    str r1, [r0]

    INTMSK主中断屏蔽寄存器(32bit),每一位对应SRCPND中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理向其写入 0xffffffff 就将 INTMSK 寄存器全部位置1,从而屏蔽对应的中断

    # if defined(CONFIG_S3C2440)
    ldr r1, =0x7fff
    ldr r0, =INTSUBMSK
    str r1, [r0]
    # endif

    INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。INTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,从而屏蔽对应的中断

    6)设置CLKDIVN

    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN
    mov r1, #3
    str r1, [r0]
    #endif

    CPU上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),CPU开始执行指令。但实际上,FCLK可以高于Fin,为了提高系统时钟,需要用软件来启用PLL。这就需要设置CLKDIVN,MPLLCON,UPLLCON这3个寄存器。

    CLKDIVN寄存器用于设置FCLK,HCLK,PCLK三者间的比例,可以根据表2.2来设置。

    表 2.2 S3C2440 的CLKDIVN寄存器格式

    CLKDIVN

    说明

    初始值

    HDIVN

    [2:1]

    00 : HCLK = FCLK/1.

    01 : HCLK = FCLK/2.

    10 : HCLK = FCLK/4 (当 CAMDIVN[9] = 0 时)

    HCLK= FCLK/8 (当 CAMDIVN[9] = 1 时)

    11 : HCLK = FCLK/3 (当 CAMDIVN[8] = 0 时)

    HCLK = FCLK/6 (当 CAMDIVN[8] = 1时)

    00

    PDIVN

    [0]

    0: PCLK = HCLK/1 1: PCLK = HCLK/2

    0

    设置CLKDIVN为5,就将HDIVN设置为二进制的10,由于CAMDIVN[9]没有被改变过,取默认值0,因此HCLK = FCLK/4。PDIVN被设置为1,因此PCLK= HCLK/2。因此分频比FCLK:HCLK:PCLK = 1:4:8 。

    MPLLCON寄存器用于设置FCLK与Fin的倍数。MPLLCON的位[19:12]称为MDIV,位[9:4]称为PDIV,位[1:0]称为SDIV。

    对于S3C2440,FCLK与Fin的关系如下面公式:

    MPLL(FCLK) = (2×m×Fin)/(p×<v:shape style="WIDTH: 11.25pt; HEIGHT: 15.75pt" id=_x0000_i1026 type="#_x0000_t75" equationxml=' 142s</w:wordDocument>'>)

    其中: m=MDIC+8,p=PDIV+2,s=SDIV

    MPLLCON与UPLLCON的值可以根据参考文献4中“PLL VALUE SELECTION TABLE”设置。该表部分摘录如下:

    表 2.3 推荐PLL值

    输入频率

    输出频率

    MDIV

    PDIV

    SDIV

    12.0000MHz

    48.00 MHz

    56(0x38)

    2

    2

    12.0000MHz

    405.00 MHz

    127(0x7f)

    2

    1

    当mini2440系统主频设置为405MHZ,USB时钟频率设置为48MHZ时,系统可以稳定运行,因此设置MPLLCON与UPLLCON为:

    MPLLCON=(0x7f<<12) | (0x02<<4) | (0x01) = 0x7f021

    UPLLCON=(0x38<<12) | (0x02<<4) | (0x02) = 0x38022

    7)关闭MMUcache

    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl cpu_init_crit
    #endif
    cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。

    下面分析一下cpu_init_crit到底做了什么:

    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    cpu_init_crit:
    	/*使数据cache与指令cache无效
    	 * flush v4 I/D caches
    	 */
    	mov	r0, #0
    	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache向c7写入0将使ICache与DCache无效  */
    	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB 向c8写入0将使TLB失效*/
    
    	/*
    	 * disable MMU stuff and caches
    	 */
    	mrc	p15, 0, r0, c1, c0, 0   /* 读出控制寄存器到r0中 */
    	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
    	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
    	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
    	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
    	mcr	p15, 0, r0, c1, c0, 0	/* 保存r0到控制寄存器 */
    
    	/*
    	 * before relocating, we have to setup RAM timing
    	 * because memory timing is board-dependend, you will
    	 * find a lowlevel_init.S in your board directory.
    	 */
    	mov	ip, lr
    	bl	lowlevel_init
    	mov	lr, ip
    	mov	pc, lr
    #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
    代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。325~327行代码将0写入c7、c8,使Cache,TLB内容无效。

    第332~337行代码关闭了MMU。这是通过修改CP15的c1寄存器来实现的,先看CP15的c1寄存器的格式(仅列出代码中用到的位):

    表 2.3 CP15的c1寄存器格式(部分)

    15

    14

    13

    12

    11

    10

    9

    8

    7

    6

    5

    4

    3

    2

    1

    0

    .

    .

    V

    I

    .

    .

    R

    S

    B

    .

    .

    .

    .

    C

    A

    M

    各个位的意义如下:

    V : 表示异常向量表所在的位置,0:异常向量在0x00000000;1:异常向量在 0xFFFF0000
    I : 0 :关闭ICaches;1 :开启ICaches
    R、S : 用来与页表中的描述符一起确定内存的访问权限
    B : 0 :CPU为小字节序;1 : CPU为大字节序
    C : 0:关闭DCaches;1:开启DCaches
    A : 0:数据访问时不进行地址对齐检查;1:数据访问时进行地址对齐检查
    M : 0:关闭MMU;1:开启MMU

    332~337行代码将c1的 M位置零,关闭了MMU

    2. 为加载bootloader的第二阶段代码准备RAM空间

    (8)初始化RAM控制寄存器

    其中的lowlevel_init就完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于mini2440,lowlevel_init在board/smdk2410/lowlevel_init.S中定义如下:

    45 #define BWSCON 0x48000000 /* 13个存储控制器的开始地址 */
    … …
    129 _TEXT_BASE:
    130 .word TEXT_BASE
    131
    132 .globl lowlevel_init
    133 lowlevel_init:
    134 /* memory control configuration */
    135 /* make r0 relative the current location so that it */
    136 /* reads SMRDATA out of FLASH rather than memory ! */
    137 ldr r0, =SMRDATA
    138 ldr r1, _TEXT_BASE
    139 sub r0, r0, r1 /* SMRDATA减 _TEXT_BASE就是13个寄存器的偏移地址 */
    140 ldr r1, =BWSCON /* Bus Width Status Controller */
    141 add r2, r0, #13*4
    142 0:
    143 ldr r3, [r0], #4 /*将13个寄存器的值逐一赋值给对应的寄存器*/
    144 str r3, [r1], #4
    145 cmp r2, r0
    146 bne 0b
    147
    148 /* everything is fine now */
    149 mov pc, lr
    150
    151 .ltorg
    152 /* the literal pools origin */
    153
    154 SMRDATA: /* 下面是13个寄存器的值 */
    155 .word … …
    156 .word … …
    … …

    lowlevel_init初始化了13个寄存器来实现RAM时钟的初始化。lowlevel_init函数对于U-Boot从NAND Flash或NOR Flash启动的情况都是有效的

    U-Boot.lds链接脚本有如下代码:

    .text :
    {
    cpu/arm920t/start.o (.text)
    … …
    }

    board/smdk2410/lowlevel_init.o也在U-Boot的前4KB的代码中

    U-Boot在NAND Flash启动时,lowlevel_init.o 将自动被读取到CPU内部4KB的内部RAM中。因此第137~146行的代码将从CPU内部RAM中复制寄存器的值到相应的寄存器中

    对于U-Boot在NOR Flash启动的情况,由于U-Boot链接时确定的地址是U-Boot在内存中的地址,而此时U-Boot还在NOR Flash中,因此还需要在NOR Flash中读取数据到RAM中

    由于NOR Flash的开始地址是0,而U-Boot的加载到内存的起始地址是 TEXT_BASE,SMRDATA 标号在Flash的地址就是 SMRDATA-TEXT_BASE

    综上所述,lowlevel_init 的作用就是将 SMRDATA 开始的13个值复制给开始地址 [BWSCON] 的13个寄存器,从而完成了存储控制器的设置

    3. 复制bootloader的第二阶段代码到RAM空间中

    9)复制U-Boot第二阶段代码到RAM

    cpu/arm920t/start.S原来的代码是只支持从NOR Flash启动的,经过修改现在U-Boot在NOR Flash和NAND Flash上都能启动了,实现的思路是这样的:

    bl bBootFrmNORFlash /* 判断U-Boot是在NAND Flash还是NOR Flash启动 */
    cmp r0, #0 /* r0存放bBootFrmNORFlash函数返回值,若返回0表示NAND Flash启动,否则表示在NOR Flash启动 */
    beq nand_boot /* 跳转到NAND Flash启动代码 */
    /* NOR Flash启动的代码 */
    b stack_setup /*跳过NAND Flash启动的代码 */
    nand_boot:
    /* NAND Flash启动的代码 */
    stack_setup:
    /*其他代码 */

    其中bBootFrmNORFlash函数作用是判断U-Boot是在NAND Flash启动还是NOR Flash启动,若在NOR Flash启动则返回1,否则返回0。根据ATPCS规则,函数返回值会被存放在r0寄存器中,因此调用bBootFrmNORFlash函数后根据r0的值就可以判断U-Boot在NAND Flash启动还是NOR Flash启动。bBootFrmNORFlash函数在board/samsung/mini2440/nand_read.c中定义如下:

    int bBootFrmNORFlash(void)
    {
    volatile unsigned int *pdw = (volatile unsigned int *)0;
    unsigned int dwVal;
    dwVal = *pdw; /*先记录下原来的数据 */
    *pdw = 0x12345678;
    if (*pdw != 0x12345678) /* 写入失败,说明是在NOR Flash启动 */
    {
    return 1;
    }
    else /* 写入成功,说明是在NAND Flash启动 */
    {
    *pdw = dwVal; /* 恢复原来的数据 */
    return 0;
    }
    }

    无论是从NOR Flash还是从NAND Flash启动,地址0处为U-Boot的第一条指令“ b reset”
    对于从NAND Flash启动的情况,其开始4KB的代码会被自动复制到CPU内部4K内存中,因此可以通过直接赋值的方法来修改
    对于从NOR Flash启动的情况,NOR Flash的开始地址即为0,必须通过一定的命令序列才能向NOR Flash中写数据,所以可以根据这点差别来分辨是从NAND Flash还是NOR Flash启动:向地址0写入一个数据,然后读出来,如果发现写入失败的就是NOR Flash,否则就是NAND Flash

    下面来分析NOR Flash启动部分代码:

    208 adr r0, _start /* r0<- current position of code */
    209 ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
    /* 判断U-Boot是否是下载到RAM中运行,若是,则不用 再复制到RAM中了,这种情况通常在调试U-Boot时才发生 */
    210 cmp r0, r1 /*_start等于_TEXT_BASE说明是下载到RAM中运行 */
    211 beq stack_setup
    212 /* 以下直到nand_boot标号前都是NOR Flash启动的代码 */
    213 ldr r2, _armboot_start
    214 ldr r3, _bss_start
    215 sub r2, r3, r2 /* r2<- size of armboot */
    216 add r2, r0, r2 /* r2<- source end address */
    217 /* 搬运U-Boot自身到RAM中*/
    218 copy_loop:
    219 ldmia r0!, {r3-r10} /* 从地址为[r0]的NOR Flash中读入8个字的数据 */
    220 stmia r1!, {r3-r10} /* 将r3至r10寄存器的数据复制给地址为[r1]的内存 */
    221 cmp r0, r2 /* until source end addreee [r2] */
    222 ble copy_loop
    223 b stack_setup /* 跳过NAND Flash启动的代码 */

    下面再来分析NAND Flash启动部分代码:

    nand_boot:
    mov r1, #NAND_CTL_BASE
    ldr r2, =( (7<<12)|(7<<8)|(7<<4)|(0<<0) )
    str r2, [r1, #oNFCONF] /* 设置NFCONF寄存器 */
    /*设置NFCONT,初始化ECC编/解码器,禁止NAND Flash片选 */
    ldr r2, =( (1<<4)|(0<<1)|(1<<0) )
    str r2, [r1, #oNFCONT]
    ldr r2, =(0x6) /* 设置NFSTAT */
    str r2, [r1, #oNFSTAT]
    /*复位命令,第一次使用NAND Flash前复位 */
    mov r2, #0xff
    strb r2, [r1, #oNFCMD]
    mov r3, #0
    /* 为调用C函数nand_read_ll准备堆栈 */
    ldr sp, DW_STACK_START
    mov fp, #0
    /* 下面先设置r0至r2,然后调用nand_read_ll函数将U-Boot读入RAM */
    ldr r0, =TEXT_BASE /* 目的地址:U-Boot在RAM的开始地址 */
    mov r1, #0x0 /* 源地址:U-Boot在NAND Flash中的开始地址 */
    mov r2, #0x30000 /* 复制的大小,必须比u-boot.bin文件大,并且必须是NAND Flash块大小的整数倍,这里设置为0x30000(192KB) */
    bl nand_read_ll /*跳转到nand_read_ll函数,开始复制U-Boot到RAM */
    tst r0, #0x0 /*检查返回值是否正确 */
    beq stack_setup
    bad_nand_read:
    loop2: b loop2 //infinite loop
    .align 2
    DW_STACK_START: .word STACK_BASE+STACK_SIZE-4
    其中NAND_CTL_BASE,oNFCONF等在include/configs/mini2440.h中定义如下:
    #define NAND_CTL_BASE 0x4E000000 // NAND Flash控制寄存器基址
    #define STACK_BASE 0x33F00000 //base address of stack
    #define STACK_SIZE 0x8000 //size of stack
    #define oNFCONF 0x00 /* NFCONF相对于NAND_CTL_BASE偏移地址 */
    #define oNFCONT 0x04 /* NFCONT相对于NAND_CTL_BASE偏移地址*/
    #define oNFADDR 0x0c /* NFADDR相对于NAND_CTL_BASE偏移地址*/
    #define oNFDATA 0x10 /* NFDATA相对于NAND_CTL_BASE偏移地址*/
    #define oNFCMD 0x08 /* NFCMD相对于NAND_CTL_BASE偏移地址*/
    #define oNFSTAT 0x20 /* NFSTAT相对于NAND_CTL_BASE偏移地址*/
    #define oNFECC 0x2c /* NFECC相对于NAND_CTL_BASE偏移地址*/

    NAND Flash各个控制寄存器的设置在S3C2440的数据手册有详细说明,这里就不介绍了

    代码中nand_read_ll函数的作用是在NAND Flash中搬运U-Boot到RAM,该函数在board/samsung/mini2440/nand_read.c中定义

    NAND Flash根据page大小可分为2种: 512B/page和2048B/page的。这两种NAND Flash的读操作是不同的。因此就需要U-Boot识别到NAND Flash的类型,然后采用相应的读操作,也就是说nand_read_ll函数要能自动适应两种NAND Flash

    参考S3C2440的数据手册可以知道:根据NFCONF寄存器的Bit3(AdvFlash (Read only))和Bit2 (PageSize (Read only))可以判断NAND Flash的类型。Bit2、Bit3与NAND Flash的block类型的关系如下表所示:

    表 2.4 NFCONF的Bit3、Bit2与NAND Flash的关系

    Bit2Bit3

    0

    1

    0

    256 B/page

    512 B/page

    1

    1024 B/page

    2048 B/page

    由于的NAND Flash只有512B/page和2048 B/page这两种,因此根据NFCONF寄存器的Bit3即可区分这两种NAND Flash了

    完整代码见board/samsung/mini2440/nand_read.c中的nand_read_ll函数,这里给出伪代码:

    int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
    {
    //根据NFCONF寄存器的Bit3来区分2种NAND Flash
    if( NFCONF & 0x8 ) /* Bit是1,表示是2KB/page的NAND Flash */
    {
    
    读取2K block 的NAND Flash
    
    }
    else /* Bit是0,表示是512B/page的NAND Flash */
    {
    /
    读取512B block 的NAND Flash
    /
    }
    return 0;
    }

    4. 设置好堆栈

    10)设置堆栈

    /* 设置堆栈*/
    stack_setup:
    ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
    sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
    sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 跳过全局数据区 */
    #ifdef CONFIG_USE_IRQ
    sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    #endif
    sub sp, r0, #12 /* leave 3 words for abort-stack */

    只要将sp指针指向一段没有被使用的内存就完成栈的设置了。根据上面的代码可以知道U-Boot内存使用情况了,如下图所示:

    图2.2 U-Boot内存使用情况

    11)清除BSS

    clear_bss:
    ldr r0, _bss_start /* BSS段开始地址,在u-boot.lds中指定*/
    ldr r1, _bss_end /* BSS段结束地址,在u-boot.lds中指定*/
    mov r2, #0x00000000
    clbss_l:str r2, [r0] /* 将bss段清零*/
    add r0, r0, #4
    cmp r0, r1
    ble clbss_l
    初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。应该将这些变量的初始值赋为0,否则这些变量的初始值将是一个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果

    12)跳转到第二阶段代码入口

    ldr pc, _start_armboot
    _start_armboot: .word start_armboot

    跳转到第二阶段代码入口start_armboot处


    二、 uboot第二阶段代码分析

    start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。U-Boot启动第二阶段流程如下:



    图 2.3 U-Boot第二阶段执行流程

    在分析start_armboot函数前先来看看一些重要的数据结构:

    1)gd_t结构体

    U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义如下:
    typedef struct global_data {
    bd_t *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long have_console; /* serial_init() was called */
    unsigned long env_addr; /* Address of Environment struct */
    unsigned long env_valid; /* Checksum of Environment valid? */
    unsigned long fb_base; /* base address of frame buffer */
    void **jt; /* jump table */
    } gd_t;
    U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
    #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
    DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了
    根据U-Boot内存使用图中可以计算gd的值:
    gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)

    (2)bd_t结构体

    bd_t在include/asm-arm.u/u-boot.h中定义如下:
    typedef struct bd_info {
    int bi_baudrate; /*串口通讯波特率 */
    unsigned long bi_ip_addr; /* IP 地址*/
    struct environment_s *bi_env; /*环境变量开始地址 */
    ulong bi_arch_number; /* 开发板的机器码 */
    ulong bi_boot_params; /* 内核参数的开始地址 */
    struct /* RAM配置信息 */
    {
    ulong start;
    ulong size;
    }bi_dram[CONFIG_NR_DRAM_BANKS];
    } bd_t;
    U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表

    (3)init_sequence数组

    U-Boot使用一个数组init_sequence来存储对于大多数开发板都要执行的初始化函数的函数指针。init_sequence数组中有较多的编译选项,去掉编译选项后init_sequence数组如下所示:
    typedef int (init_fnc_t) (void);
    init_fnc_t *init_sequence[] = {
    board_init, /*开发板相关的配置--board/samsung/mini2440/mini2440.c */
    timer_init, /* 时钟初始化-- cpu/arm920t/s3c24x0/timer.c */
    env_init, /*初始化环境变量--common/env_flash.c 或common/env_nand.c*/
    init_baudrate, /*初始化波特率-- lib_arm/board.c */
    serial_init, /* 串口初始化-- drivers/serial/serial_s3c24x0.c */
    console_init_f, /* 控制通讯台初始化阶段1-- common/console.c */
    display_banner, /*打印U-Boot版本、编译的时间-- gedit lib_arm/board.c */
    dram_init, /*配置可用的RAM-- board/samsung/mini2440/mini2440.c */
    display_dram_config, /* 显示RAM大小-- lib_arm/board.c */
    NULL,
    };
    其中的board_init函数在board/samsung/mini2440/mini2440.c中定义,该函数设置了MPLLCOM,UPLLCON,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核启动参数地址 :
    /* MINI2440开发板的机器码 */
    gd->bd->bi_arch_number = MACH_TYPE_MINI2440;
    /* 内核启动参数地址 */
    gd->bd->bi_boot_params = 0x30000100;
    其中的dram_init函数在board/samsung/mini2440/mini2440.c中定义如下:
    int dram_init (void)
    {
    /* 由于mini2440只有 */
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
    return 0;
    }
    mini2440使用2片32MB的SDRAM组成了64MB的内存,接在存储控制器的BANK6,地址空间是0x30000000~0x34000000
    在include/configs/mini2440.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE 分别被定义为0x30000000和0x04000000(64M)

    分析完上述的数据结构,下面来分析start_armboot函数:

    void start_armboot (void)
    {
    init_fnc_t **init_fnc_ptr;
    char *s;
    … …
    /*计算全局数据结构的地址gd */
    gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
    … …
    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd->bd, 0, sizeof (bd_t));
    gd->flags |= GD_FLG_RELOC;
    monitor_flash_len = _bss_start - _armboot_start;
    /* 逐个调用init_sequence数组中的初始化函数 */
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    if ((*init_fnc_ptr)() != 0) {
    hang ();
    }
    }
    /* armboot_start 在cpu/arm920t/start.S 中被初始化为u-boot.lds连接脚本中的_start */
    mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
    CONFIG_SYS_MALLOC_LEN);
    /* NOR Flash初始化 */
    #ifndef CONFIG_SYS_NO_FLASH
    /* configure available FLASH banks */
    display_flash_config (flash_init ());
    #endif /* CONFIG_SYS_NO_FLASH */
    … …
    /* NAND Flash 初始化*/
    #if defined(CONFIG_CMD_NAND)
    puts ("NAND: ");
    nand_init(); /* go init the NAND */
    #endif
    … …
    /*配置环境变量,重新定位 */
    env_relocate ();
    … …
    /*从环境变量中获取IP地址 */
    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
    stdio_init (); /* get the devices list going. */
    jumptable_init ();
    … …
    console_init_r (); /* fully init console as a device */
    … …
    /*enable exceptions */
    enable_interrupts ();
    #ifdef CONFIG_USB_DEVICE
    usb_init_slave();
    #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
    … …
    /*网卡初始化 */
    #if defined(CONFIG_CMD_NET)
    #if defined(CONFIG_NET_MULTI)
    puts ("Net: ");
    #endif
    eth_initialize(gd->bd);
    … …
    #endif
    /* 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 */
    }
    main_loop函数在common/main.c中定义。一般情况下,进入main_loop函数若干秒内没有

    U-Boot启动Linux过程

    U-Boot使用标记列表(tagged list)的方式向Linux传递参数。标记的数据结构式是tag,在U-Boot源代码目录include/asm-arm/setup.h中定义如下:
    struct tag_header {
    u32 size; /* 表示tag数据结构的联合u实质存放的数据的大小*/
    u32 tag; /* 表示标记的类型 */
    };
    struct tag {
    struct tag_header hdr;
    union {
    struct tag_core core;
    struct tag_mem32 mem;
    struct tag_videotext videotext;
    struct tag_ramdisk ramdisk;
    struct tag_initrd initrd;
    struct tag_serialnr serialnr;
    struct tag_revision revision;
    struct tag_videolfb videolfb;
    struct tag_cmdline cmdline;
    /*
    * Acorn specific
    */
    struct tag_acorn acorn;
    /*
    * DC21285 specific
    */
    struct tag_memclk memclk;
    } u;
    };
    U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的是do_bootm函数。对于Linux内核,do_bootm函数会调用do_bootm_linux函数来设置标记列表和启动内核。do_bootm_linux函数在lib_arm/bootm.c 中定义如下:
    59 int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
    60 {
    61 bd_t *bd = gd->bd;
    62 char *s;
    63 int machid = bd->bi_arch_number;
    64 void (*theKernel)(int zero, int arch, uint params);
    65
    66 #ifdef CONFIG_CMDLINE_TAG
    67 char *commandline = getenv ("bootargs"); /* U-Boot环境变量bootargs */
    68 #endif
    … …
    73 theKernel = (void (*)(int, int, uint))images->ep; /* 获取内核入口地址 */
    … …
    86 #if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    87 defined (CONFIG_CMDLINE_TAG) || \
    88 defined (CONFIG_INITRD_TAG) || \
    89 defined (CONFIG_SERIAL_TAG) || \
    90 defined (CONFIG_REVISION_TAG) || \
    91 defined (CONFIG_LCD) || \
    92 defined (CONFIG_VFD)
    93 setup_start_tag (bd); /* 设置ATAG_CORE标志 */
    … …
    100 #ifdef CONFIG_SETUP_MEMORY_TAGS
    101 setup_memory_tags (bd); /* 设置内存标记 */
    102 #endif
    103 #ifdef CONFIG_CMDLINE_TAG
    104 setup_commandline_tag (bd, commandline); /* 设置命令行标记 */
    105 #endif
    … …
    113 setup_end_tag (bd); /* 设置ATAG_NONE标志 */
    114 #endif
    115
    116 /* we assume that the kernel is in place */
    117 printf ("\nStarting kernel ...\n\n");
    … …
    126 cleanup_before_linux (); /* 启动内核前对CPU作最后的设置 */
    127
    128 theKernel (0, machid, bd->bi_boot_params); /* 调用内核 */
    129 /* does not return */
    130
    131 return 1;
    132 }
    其中的setup_start_tag,setup_memory_tags,setup_end_tag函数在lib_arm/bootm.c中定义如下:

    (1)setup_start_tag函数

    static void setup_start_tag (bd_t *bd)
    {
    params = (struct tag *) bd->bi_boot_params; /* 内核的参数的开始地址 */
    params->hdr.tag = ATAG_CORE;
    params->hdr.size = tag_size (tag_core);
    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;
    params = tag_next (params);
    }
    标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。

    (2)setup_memory_tags函数

    static void setup_memory_tags (bd_t *bd)
    {
    int i;
    /*设置一个内存标记 */
    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
    params->hdr.tag = ATAG_MEM;
    params->hdr.size = tag_size (tag_mem32);
    params->u.mem.start = bd->bi_dram[i].start;
    params->u.mem.size = bd->bi_dram[i].size;
    params = tag_next (params);
    }
    }
    setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。

    (3)setup_end_tag函数

    static void setup_end_tag (bd_t *bd)
    {
    params->hdr.tag = ATAG_NONE;
    params->hdr.size = 0;
    }
    标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。
    U-Boot设置好标记列表后就要调用内核了。但调用内核前,CPU必须满足下面的条件:
    (1) CPU寄存器的设置
    Ø r0=0
    Ø r1=机器码
    Ø r2=内核参数标记列表在RAM中的起始地址
    (2) CPU工作模式
    Ø 禁止IRQ与FIQ中断
    Ø CPU为SVC模式
    (3) 使数据Cache与指令Cache失效
    do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。cleanup_before_linux函数在cpu/arm920t/cpu.中定义:
    int cleanup_before_linux (void)
    {
    /*
    * this function is called just before we call linux
    * it prepares the processor for linux
    *
    * we turn off caches etc ...
    */
    disable_interrupts (); /* 禁止FIQ/IRQ中断 */
    /*turn off I/D-cache */
    icache_disable(); /* 使指令Cache失效 */
    dcache_disable(); /* 使数据Cache失效 */
    /*flush I/D-cache */
    cache_flush(); /* 刷新Cache */
    return 0;
    }
    由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。
    do_bootm_linux中:
    64 void (*theKernel)(int zero, int arch, uint params);
    … …
    73 theKernel = (void (*)(int, int, uint))images->ep;
    … …
    128 theKernel (0, machid, bd->bi_boot_params);
    第73行代码将内核的入口地址“images->ep”强制类型转换为函数指针。根据ATPCS规则,函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。因此第128行的函数调用则会将0放入r0,机器码machid放入r1,内核参数地址bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址
    到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行

    U-Boot添加命令的方法及U-Boot命令执行过程

    下面以添加menu命令(启动菜单)为例讲解U-Boot添加命令的方法

    (1) 建立common/cmd_menu.c

    习惯上通用命令源代码放在common目录下,与开发板专有命令源代码则放在board/<board_dir>目录下,并且习惯以“cmd_<命令名>.c”为文件名

    (2) 定义“menu”命令

    在cmd_menu.c中使用如下的代码定义“menu”命令:
    _BOOT_CMD(
    menu, 3, 0, do_menu,
    "menu- display a menu, to select the items to do something\n",
    "- display a menu, to select the items to do something"
    );
    其中U_BOOT_CMD命令格式如下:
    U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
    各个参数的意义如下:
    name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
    maxargs:命令的最大参数个数
    rep:是否自动重复(按Enter键是否会重复执行)
    cmd:该命令对应的响应函数
    usage:简短的使用说明(字符串)
    help:较详细的使用说明(字符串)
    在内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。若在include/configs/mini2440.h中定义了CONFIG_SYS_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示usage和help字段的内容,否则就只显示usage字段的内容
    U_BOOT_CMD宏在include/command.h中定义:
    #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
    cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
    “##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串
    其中的cmd_tbl_t在include/command.h中定义如下:
    struct cmd_tbl_s {
    char *name; /* 命令名 */
    int maxargs; /* 最大参数个数 */
    int repeatable; /* 是否自动重复 */
    int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 响应函数 */
    char *usage; /* 简短的帮助信息 */
    #ifdef CONFIG_SYS_LONGHELP
    char *help; /* 较详细的帮助信息 */
    #endif
    #ifdef CONFIG_AUTO_COMPLETE
    /*自动补全参数 */
    int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
    #endif
    };
    typedef struct cmd_tbl_s cmd_tbl_t;
    一个cmd_tbl_t结构体变量包含了调用一条命令的所需要的信息
    其中Struct_Section在include/command.h中定义如下:
    #define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
    凡是带有__attribute__ ((unused,section (".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息
    在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段:
    . = .;
    __u_boot_cmd_start = .; /*将 __u_boot_cmd_start指定为当前地址 */
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .; /* 将__u_boot_cmd_end指定为当前地址 */
    这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。这样只要将U-Boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在__u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了
    因此“menu”命令的定义经过宏展开后如下:
    cmd_tbl_t __u_boot_cmd_menu __attribute__ ((unused,section (".u_boot_cmd"))) = {menu, 3, 0, do_menu, "menu- display a menu, to select the items to do something\n", " - display a menu, to select the items to do something"}
    实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。编译器将该结构体放在“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的cmd_tbl_t类型结构体

    (3) 实现命令的函数

    在cmd_menu.c中添加“menu”命令的响应函数的实现。具体的实现代码略:
    int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
    /*实现代码略 */
    }

    (4) 将common/cmd_menu.c编译进u-boot.bin

    在common/Makefile中加入如下代码:
    COBJS-$(CONFIG_BOOT_MENU) += cmd_menu.o
    在include/configs/mini2440.h加入如代码:
    #define CONFIG_BOOT_MENU 1
    重新编译下载U-Boot就可以使用menu命令了

    (5)menu命令执行的过程

    在U-Boot中输入“menu”命令执行时,U-Boot接收输入的字符串“menu”,传递给run_command函数。run_command函数调用common/command.c中实现的find_cmd函数在__u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回menu命令的cmd_tbl_t结构。然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用menu命令的响应函数do_menu,从而完成了命令的执行































    展开全文
  • uboot 详细注释讲解

    千次阅读 2010-07-12 14:32:00
    声明:该贴是通过参考其他人的帖子整理出来,从中我加深了对uboot的理解,我知道对其他人一定也是有很大的帮助,不敢私藏,如果里面的注释有什么错误请给我回复,我再加以修改。有些部分可能还没解释清楚,...
  • uboot2013版本详解

    2014-12-17 21:39:19
    最近开始接触uboot,现在需要将2014.4版本uboot移植到公司armv7开发板。 在网上搜索讲uboot启动过程的文章,大多都是比较老版本的uboot,于是决定将新版uboot启动过程记录下来,和大家共享。 辛苦之作,大家共享,...
  • uboot代码详细分析.rar

    2020-08-04 00:36:16
    uboot代码详细分析 包括uboot代码启动流程,移植,内存布局 想学习uboot的很值得看,很推荐学习!!!
  • uboot的作用和功能

    万次阅读 多人点赞 2019-02-27 10:57:35
    uboot是用来干什么的,有什么作用? uboot 属于bootloader的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存中,启动内核 所以,由上面描述的,就知道,UBOOT需要具有读写flash的能力...
  • uboot初探

    千次阅读 2018-10-05 19:33:32
    总结:  1 uboot其实就是个大程序,大的裸机程序  2 uboot 和Linux内核不一样,他没有多任务调度运行的机制,不能实现任务调度等操作系统应用功能...uboot起初是为powerpc开发的启动程序,uboot是个bootloade...
  • (1)uboot详解——板子刚上电时都干了些什么

    万次阅读 多人点赞 2015-11-09 14:02:08
    电子产品如果没有了电,就跟废品没什么区别,是电赋予了他们生命,然而程序则是他们的灵魂。 小时候一直很好奇,一个个死板的电子产品为什么一上电以后就能够工作了呢?为什么一个小小芯片就能够运行我们编写的程序...
  • openwrt下编译mt7628的uboot

    万次阅读 2015-09-06 10:13:48
    使用git下载源码 git clone ...将下载的源码拷贝到openwrt源码下的package/uboot目录下,如下图 在menuconfig中选择uboot-mt7628这个package 执行下面的命令编译uboot make package/uboot-mt7628/prepar
  • 新路程------在uboot中配置ip地址

    千次阅读 2017-05-10 13:49:11
    在/rtc/rc.d/rc.local中加入这么一句话即可 ifconfig eth0 192.168.21.250 &
  • uboot DDR 配置 修改

    千次阅读 2017-10-07 15:30:06
    1.修改.h头文件的配置 2.修改MMU映射 还有lowlevel_init.S
  • u-boot 中读写寄存器

    千次阅读 2016-05-28 09:52:53
    如下所示: mw 0xE0003170 0xa0000000;mw 0xE0003170 0x40010000;md 0xE0003170 1
  • uboot中打开debug调试信息的方法

    千次阅读 2018-10-30 14:40:06
    很简单,找到uboot的根目录/include/common.h中 在这个位置定义一个DEBUG然后重新编译即可。 #define DEBUG
  • zynq-7000学习笔记(二)——编译uboot

    万次阅读 热门讨论 2016-08-13 09:53:51
    PC平台:ubuntu 14.04 Xilinx设计开发套件:Xilinx_vivado_sdk_2016.2 1、下载u-boot源代码 ...直接通过git下载,或者下载zip压缩包 2、设置环境变量 ARCH和CROSS_COMPILE 3、修改include/configs/zynq-
  • 修改arm开发板ip和uboot的环境变量ip

    万次阅读 2016-07-19 11:49:30
    开发板ip和uboot环境变量ip修改
  • uboot 源码官方下载地址

    万次阅读 2018-04-17 20:42:42
    U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目。从FADSROM、8xxROM、PPCBOOT逐步发展演化而来所有版本的u-boot源代码压缩包都可以在ftp://ftp.denx.de/pub/u-boot/下载。关于u-boot源代码的信息...
1 2 3 4 5 ... 20
收藏数 38,560
精华内容 15,424
关键字:

uboot