Uboot启动与移植

计算机系统的组成部件非常多,不同的计算机系统组成部件也不同。但是所有的计算机系统运行时需要的主要核心部件都是3个东西:CPU + 外部存储器(Flash/硬盘) + 内部存储器(DDR SDRAM/SDRAM/SRAM)。而一般的PC机启动过程为:PC上电后先执行BIOS程序(实际上PC的BIOS就是NorFlash),BIOS程序负责初始化DDR内存,负责初始化硬盘,然后从硬盘上将OS镜像读取到DDR中,然后跳转到DDR中去执行OS直到启动(OS启动后BIOS就无用了)。

嵌入式系统和PC机的启动过程几乎没有两样,只是BIOS成了uboot,硬盘成了Flash,大多数处理器在上电的时候都会有默认的指令执行位置,如ARM架构的处理器会从0x00000000地址开始读取第一条指令。

Boot Loader

简单来说BootLoader是一段小程序,它在系统上电时执行,通过这段小程序可以将硬件设备进行初始化,如CPU、SDRAM、Flash、串口、网络 等,初始化完毕后调用操作系统内核

每种不同的 CPU 体系结构都有不同的Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持** ARM ** 体系结构和MIPS 体系结构。除了依赖于 CPU的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置,Boot Loader 是严重地依赖于硬件而实现的。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。

主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行输入、输出,比如:输出打印信息到串口,从串口读取用户控制字符等。

BootLoader 的启动过程可分为单阶段(Single-Stage)和多阶段(Multi-Stage)两种。通常多阶段的Boot Loader 具有更复杂的功能,更好的可移植性。从固态存储设备上启动的Boot Loader 大多采用两阶段。

⭐Boot Loader一般分为2部分,汇编部分C语言部分 ,汇编部分执行简单的硬件初始化,C语言部分负责复制数据,设置启动参数,串口通信等功能.

  • Boot Loader的核心功能就是引导操作系统,部分工作如下

    • 初始化部分硬件,包括时钟、内存等等
    • 加载内核到内存上
    • 加载文件系统、atags或者dtb到内存上
    • 根据操作系统启动要求正确配置好一些硬件
    • 启动操作系统
  • 部分Boot Loader还支持monitor功能,提供了更多的命令行接口,具体部分功能如下:

    • 进行调试
    • 读写内存
    • 烧写Flash
    • 配置环境变量
    • 命令引导操作系统

Uboot

U-Boot部分源码就是根据相应的Linux内核源程序进行简化而形成的,尤其是一些设备的驱动程序,这从U-Boot源码的注释中能体现这一点。
Uboot 属于Boot Loader的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存中,启动内核,即Uboot需要具有读写flash 的能力。
编译好的u-boot进行移植烧录,将一编译好的u-boot.bin文件烧写到开发板的flash存储器中,在开发板上电后,板载cpu来引导internal iROM(含有烧写的代码)引导flash开始执行工作(iRAM中封装了flash的启动函数),波动拨码开关来告诉系统从哪个flash内存中哪个存储器启动(nand、sd/mmc、eMMC、usb),检测boot loader是否有效并将boot loader读取到SRAM中,
在SRAM将boot loader启动的同时初始化DRAM存储器以扩展内存,之后再boot loader的引导下将u-boot的镜像读取到DRAM中,u-boot启动开始等待指令输出或者加载启动内核。

总结:
uboot刚开始被放到flash中,板子上电后,会自动把其中的一部分代码拷到内存中执行,这部分代码负责把剩余的uboot代码拷到内存中,然后uboot代码再把kernel部分代码也拷到内存中,并且启动,内核启动后,挂着根文件系统,执行应用程序。

  • uboot主要作用是用来启动操作系统内核。体现在uboot最后一句代码就是启动内核。

  • uboot还要负责部署整个计算机系统。体现在uboot最后的传参。

  • uboot中还有操作Flash等板子上硬件的驱动。例如串口要打印,ping网络成功,擦除、烧写flash是否成功等。

  • uboot还得提供一个命令行界面供人来操作。

当系统开机之后,在开机界面会有个数字倒计时,若倒计时结束没有进行操作,uboot启动默认的操作系统,并将控制块交给操作系统。若倒计时结束之前有操作,比如按下回车键,就会进入下载模式,开发者可以在下载模式下使用uboot命令自由操控uboot。

** uboot必须解决哪些问题?**

  • 自身可开机直接启动

    • 一般的SoC都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等•••••uboot要能够开机启动,必须根据具体的SoC的启动设计来设计uboot

    • uboot必须进行和硬件相对应的代码级别的更改和移植,才能够保证可以从相应的启动介质启动。uboot中第一阶段的start.S文件中具体处理了这一块。

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

    • uboot的终极目标就是启动内核。

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

  • 能提供系统部署功能

    • uboot必须能够被人借助而完成整个系统(包括uboot、kernel、rootfs等的镜像)在Flash上的烧录下载工作。

    • 裸机教程中刷机(ARM裸机第三部分)就是利用uboot中的fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。

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

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

    • SoC级(譬如串口)就是SoC内部外设,板级就是SoC外面开发板上面的硬件(譬如网卡、iNand)

  • uboot的"生命周期"

    • uboot的生命周期就是指:uboot什么时候开始运行,什么时候结束运行。

    • uboot本质上是一个裸机程序(不是操作系统),一旦uboot开始SoC就会单纯运行uboot(意思就是uboot运行的时候别的程序是不可能同时运行的),一旦uboot结束运行则无法再回到uboot(所以uboot启动了内核后uboot自己本身就死了,要想再次看到uboot界面只能重启系统。重启并不是复活了刚才的uboot,重启只是uboot的另一生)

    • uboot的入口和出口。uboot的入口就是开机自动启动,uboot的唯一出口就是启动内核。uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了。

总结:uboot的一切都是为了启动内核。

Uboot启动流程

Uboot就是将start.o和大量的built-in.o链接在一起。
通过uboot-lds可以看到入口地址为_start

启动流程:
1)设置CPU为管理模式
2)关看门狗
3)关中断
4)设置时钟频率
5)关mmu,初始化各个bank
6)进入board_init_f()函数 (初始化定时器,GPIO,串口等,划分内存区域)
7)重定位 复制uboot,然后修改SDRAM上的uboot链接地址)
8)清bss
9)跳转到board_init_r()函数,启动流程结束

_start是整个uboot的入口

arch级初始化:
⭐部分硬件初始化 -> 加载完整的uboot到RAM

  • 设置CPU为SVC 状态 (超级保护模式)即M4~M1=10011

    • 除了用户模式之外的其他6种处理器模式称为特权模式。特权模式下,程序可以访问所有的系统资源(除了特定模式下的影子寄存器),也可以任意地进行处理器模式的切换。特权模式中,除系统模式外,其他5种模式又称为异常模式。
      • 用户模式下访问的资源受限,故不能使用用户模式
      • 系统模式的优先级低于异常模式,故不使用系统模式
      • 快中断模式、中断模式、中止模式、未定义模式用于特殊场景下由CPU自动切入,故不使用
    • 所以需要使用SVC模式。
  • 关中断(关闭FIQ和IRQ两个中断)

    • 在启动过程中,中断环境并没有完全准备好,也就是中断向量表和中断处理函数并没有完成设置,一旦有中断产生,可能会导致预想不到的问题,或者是程序跑飞。因此,在准备好中断环境之前,需要关闭所有中断。
  • 协处理器进行初始化

    • 关flash,清cache,清TLB(旁路转换缓冲,即虚拟地址到物理地址的转换表),关MMU(内存管理单元。它的主要功能就是实现虚拟地址到物理地址的映射。),初始化存储控制器,初始化后内存控制器才可以使用
  • 关键寄存器的初始化

    • 检查一些复位状态
    • 关闭看门狗
    • 系统时钟的初始化
    • 内存、DDR的初始化
    • 串口初始化(可选)
    • Nand flash的初始化

板级初始化

板级初始化代码的入口就是_main。

  • 堆栈、gd、early malloc空间的分配、初始化

    • board_init_f_alloc_reserve
      • 留出早期malloc和gd(global_data)内存区域
    • board_init_f_init_reserve
      • 初始化gd,初始化gd(清零),再做16字节对齐,最终sp指向0X0091FB00
  • 进入board_init_f() 函数 ( 核心就是init_sequence_f中定义的函数)

    • 清空gd指向的结构体、通过init_sequence函数数组,来初始化各个函数以及逐步填充gd结构体,最后划分内存区域,将数据保存在gd里
      • gd一个全局变量,是用来传递给内核的参数
    • 初始化DDR,定时器,完成代码拷贝,初始化波特率串口,打印前面暂存在缓冲区的数据 此时sp和gd是存放在DDR上了。而不是内部的RAM。
    • uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
    • ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
    • 注意 :必须保证上述的函数都正确地返回0值,否则会导致hang
  • 重定向relocate_code、relocate_vectors

    • 如果链接地址与加载地址不同,则运行时PC得到的值与实际指令/数据存放的地址就不相同,即该处没有有效的可执行指令或读出错误的数据。因此,当加载地址!=链接地址时,就需要重定位。
    • relocate_code函数主要用于代码拷贝 ,即负责将uboot拷贝到新的地方,完成代码拷贝(复制uboot,然后修改SDRAM上的uboot链接地址),在relocate_code函数之前还有语句ldr r0,[r9,#GD_RELOCADDR],即r0=gd-> relocaddr= 0X9FF47000,uboot重定位后的首地址
      • 注意: 直接将uboot从0X87800000拷贝至其他地方后,函数调用、全局变量引用可能会出问题。uboot采用 位置无关码 来处理该类问题(简单说采用相对地址寻址,而不是采用绝对地址寻址,并且重定位后需要将Label+offset)。在使用 ld 进行链接的时候使用选项”- pie” 生成位置无关的可执行文件。具体为.rel.dyn段(当uboot对自身进行relocate之后,此时全局变量的绝对地址已经发生变化,如果函数按照原来的label去获取全局变量的地址的时候,这个地址其实是relocate之前的地址。因此,在relocate的过程中需要对全局变量的label中的地址值进行修改,所以uboot将这些label的地址全部维护在.rel.dyn段中)。
    • relocate_vectors函数主要用于重定位向量表 。relocate_vectors函数位于arch/arm/lib/relocate.S中,用于设置VBAR寄存器为重定位后的中断向量表起始地址
  • 重定位完成后,清除bss段

    • BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。
    • 做完了重定位,把代码copy到了sdram上,然后sdram上紧接着的地址就是.bss的基地址了,这时候bss段的这块内存没有经过任何处理,所以是随机的。需要进行清除sdram上.bss段的数据,因为我们知道bss是未初始化和初始值为0的全局变量。uboot里的清除BSS段的代码,其实也是在为bss段分配空间
    • 不要在C代码中对未初始化的全局变量赋0,因为BSS段会被自动赋0.
  • board_init_r()函数 ( 核心就是init_sequence_r中定义的函数)

    • 前面board_init_f函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r(这里的外设更多包括EMMC,NANDFLASH)来完成的
    • run_main_loop() 函数
      • 该函数为Uboot的最终执行函数,无论是加载kernel还是uboot的命令行体系,均由此实现。uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
      • uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux内 核,这个功能就是由run_main_loop函数来完成的。

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

1)uboot启动后大部分时间和工作都是在shell下完成的(譬如uboot要部署系统要在shell下输命令、要设置环境变量也得在命令行地下,要启动内核也要在命令行底下敲命令)。

2)命令就是uboot的shell中可以识别的各种命令。uboot中有几十个命令,其中有一些常用另一些不常用(我们还可以自己给uboot添加命令),后面会用几节课时间来依次学习uboot中常用命令。

3)uboot的环境变量和操作系统的环境变量工作原理和方式几乎完全相同。uboot在设计时借助了操作系统的设计理念(命令行工作方式借鉴了linux终端命令行,环境变量借鉴了操作系统的环境变量,uboot的驱动管理几乎完全照抄了linux的驱动框架)。

4)环境变量可以被认为是系统的全局变量,环境变量名都是系统内置的(认识就认识,不认识就不认识,这部分是系统自带的默认的环境变量,譬如PATH;但是也有一部分环境变量是自己添加的,自己添加的系统就不认识但是我们自己认识)。系统或者我们自己的程序在运行时可以通过读取环境变量来指导程序的运行。这样设计的好处就是灵活,譬如我们要让一个程序更改运行方法,不用去重新修改程序代码再重新编译运行,而只要修改相应的环境变量就可以了。

5)环境变量就是运行时的配置属性。

移植要考虑的问题:

移植要考虑的主要问题就是NOR flash。从上述分析知道,u-boot启动时要执行flash_init() 检测flash的ID号,大小,secotor起始地址表和保护状态表,这些信息全部保存在flash_info_t、flash_info[CFG_MAX_FLASH_BANKS]中。

另外,u-boot中有一些命令如saveenvt需要要擦写flash,间接调用两个函数:flash_erase和write_buff。在board/smdk2410/flash.c实现了与板子相关的nor flash函数操作。由于write_buffer中调用了write_hword去具体写入一个字到flash中,这个函数本身是与硬件无关的,所以与硬件密切相关的三个需要重写的函数是flash_init, flash_erase,write_hword

这些代码在与nor flash相关的命令中都会间接被调用。所以u-boot可移植性的另一个方面就是规定一些函数调用接口和全局变量 ,这些函数的实现是硬件相关的,移植时只需要实现这些函数。

而全局变量是具体硬件无关的。u-boot在通用目录中实现其余与硬件无关的函数,这些函数就只与全局变量和函数接口打交道了。 通过编译选项设置来灵活控制是否需要编译通用部分。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值